diff --git a/.secrets.baseline b/.secrets.baseline index b04a7e4..6000923 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -124,27 +124,26 @@ }, { "path": "detect_secrets.filters.heuristic.is_templated_secret" + }, + { + "path": "detect_secrets.filters.regex.should_exclude_file", + "pattern": [ + "static/blog/tiptap\\.js$", + "static/css/tailwind\\.min\\.css$", + "package-lock\\.json$", + "\\.venv/", + "node_modules/" + ] } ], "results": { - ".github/workflows/tests.yml": [ - { - "type": "Secret Keyword", - "filename": ".github/workflows/tests.yml", - "hashed_secret": "3c90011fe8d955a8938382f77cafa2016b03413e", - "is_verified": false, - "line_number": 34, - "is_secret": false - } - ], "account/api/serializers.py": [ { "type": "Secret Keyword", "filename": "account/api/serializers.py", "hashed_secret": "ba82d7311b12e96451206a9202f2300add40126a", "is_verified": false, - "line_number": 38, - "is_secret": false + "line_number": 38 } ], "account/tests.py": [ @@ -153,56 +152,49 @@ "filename": "account/tests.py", "hashed_secret": "1c58bd92003bbaa0538e249fff6ee19a270dec5f", "is_verified": false, - "line_number": 74, - "is_secret": false + "line_number": 74 }, { "type": "Secret Keyword", "filename": "account/tests.py", "hashed_secret": "1816851d10187b57a93235b52495e615629f906d", "is_verified": false, - "line_number": 103, - "is_secret": false + "line_number": 103 }, { "type": "Secret Keyword", "filename": "account/tests.py", "hashed_secret": "0fa86f7cd4925d1bc1f299fefdaeb3cede77592f", "is_verified": false, - "line_number": 146, - "is_secret": false + "line_number": 146 }, { "type": "Secret Keyword", "filename": "account/tests.py", "hashed_secret": "d8ecf7db8fc9ec9c31bc5c9ae2929cc599c75f8d", "is_verified": false, - "line_number": 176, - "is_secret": false + "line_number": 176 }, { "type": "Secret Keyword", "filename": "account/tests.py", "hashed_secret": "0d8b28805975effded2c628b96d75cd3b47bbdcf", "is_verified": false, - "line_number": 257, - "is_secret": false + "line_number": 257 }, { "type": "Secret Keyword", "filename": "account/tests.py", "hashed_secret": "1151a8c33d6ae2daf21ad0d466488e707c1c7f8d", "is_verified": false, - "line_number": 281, - "is_secret": false + "line_number": 281 }, { "type": "Secret Keyword", "filename": "account/tests.py", "hashed_secret": "bdb8465ce041d94a0e490564f2162dcc87d4a46a", "is_verified": false, - "line_number": 289, - "is_secret": false + "line_number": 289 } ], "blog/tests.py": [ @@ -211,8 +203,7 @@ "filename": "blog/tests.py", "hashed_secret": "1c58bd92003bbaa0538e249fff6ee19a270dec5f", "is_verified": false, - "line_number": 13, - "is_secret": false + "line_number": 13 } ], "blog/tests_api.py": [ @@ -221,8 +212,7 @@ "filename": "blog/tests_api.py", "hashed_secret": "1c58bd92003bbaa0538e249fff6ee19a270dec5f", "is_verified": false, - "line_number": 32, - "is_secret": false + "line_number": 32 } ], "settings.ini.example": [ @@ -231,10 +221,9 @@ "filename": "settings.ini.example", "hashed_secret": "29b8dca3de5ff27bcf8bd3b622adf9970f29381c", "is_verified": false, - "line_number": 2, - "is_secret": false + "line_number": 2 } ] }, - "generated_at": "2026-05-14T13:07:32Z" + "generated_at": "2026-05-14T23:21:05Z" } diff --git a/blog/forms.py b/blog/forms.py index a4899fc..3e8a35f 100644 --- a/blog/forms.py +++ b/blog/forms.py @@ -1,6 +1,7 @@ from django import forms from blog.models import BlogPost, Comment +from blog.widgets import TiptapWidget class CommentForm(forms.ModelForm): @@ -20,6 +21,7 @@ class Meta: fields = ['title', 'body', 'image', 'category', 'tags', 'status'] widgets = { 'tags': forms.CheckboxSelectMultiple(), + 'body': TiptapWidget(), } @@ -30,10 +32,10 @@ class Meta: fields = ['title', 'body', 'image', 'category', 'tags', 'status'] widgets = { 'tags': forms.CheckboxSelectMultiple(), + 'body': TiptapWidget(), } def save(self, commit=True): - # Keep existing image if no new one uploaded if not self.cleaned_data.get('image'): self.cleaned_data['image'] = self.instance.image return super().save(commit=commit) diff --git a/blog/migrations/0009_alter_blogpost_body.py b/blog/migrations/0009_alter_blogpost_body.py index 7b54825..c1dba8d 100644 --- a/blog/migrations/0009_alter_blogpost_body.py +++ b/blog/migrations/0009_alter_blogpost_body.py @@ -1,7 +1,6 @@ # Generated by Django 5.2.12 on 2026-04-02 19:45 -import django_ckeditor_5.fields -from django.db import migrations +from django.db import migrations, models class Migration(migrations.Migration): @@ -14,6 +13,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='blogpost', name='body', - field=django_ckeditor_5.fields.CKEditor5Field(blank=True, max_length=20000), + field=models.TextField(blank=True, max_length=20000), ), ] diff --git a/blog/migrations/0014_alter_blogpost_body.py b/blog/migrations/0014_alter_blogpost_body.py new file mode 100644 index 0000000..009fa79 --- /dev/null +++ b/blog/migrations/0014_alter_blogpost_body.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.14 on 2026-05-14 18:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0013_rebackfill_body_plain_unescape'), + ] + + operations = [ + migrations.AlterField( + model_name='blogpost', + name='body', + field=models.TextField(blank=True, max_length=20000), + ), + ] diff --git a/blog/models.py b/blog/models.py index 5379655..679a411 100644 --- a/blog/models.py +++ b/blog/models.py @@ -8,7 +8,6 @@ from django.dispatch import receiver from django.utils.html import strip_tags from django.utils.text import slugify -from django_ckeditor_5.fields import CKEditor5Field def upload_location(instance, filename): @@ -49,7 +48,7 @@ class BlogPost(models.Model): ] title = models.CharField(max_length=50, null=False, blank=True) - body = CKEditor5Field(max_length=20000, blank=True, config_name='default') + body = models.TextField(max_length=20000, blank=True) # Plain-text mirror of body, populated in pre_save. Search uses this so HTML # tag names (div, class, style) don't leak as matches against the rich field. body_plain = models.TextField(blank=True, editable=False) diff --git a/blog/templates/blog/widgets/tiptap.html b/blog/templates/blog/widgets/tiptap.html new file mode 100644 index 0000000..1cd3a56 --- /dev/null +++ b/blog/templates/blog/widgets/tiptap.html @@ -0,0 +1,19 @@ +
hi
') + self.assertIn('') + self.assertNotIn('', + author=self.user, category=self.category, status='published', + ) + r = self.client.get(reverse('blog:detail', args=[post.slug])) + self.assertEqual(r.status_code, 200) + # The sanitiser must strip the ', r.content) diff --git a/blog/widgets.py b/blog/widgets.py new file mode 100644 index 0000000..54f40ab --- /dev/null +++ b/blog/widgets.py @@ -0,0 +1,21 @@ +from django import forms + + +class TiptapWidget(forms.Textarea): + """Renders a hidden