CKEditor field not validating correctly

I have an issue getting a form to validate correctly when integrating CKEditor and Django Forms.

All other form fields validate correctly except for the CKEditor form which for some reason is allowed submit ‘blank’ when the setting in Models.py is null=False. The server side validation appears to partially work though, as the form is not saved to the database, in accordance with the if form.is_valid(): statement in Views.py.

models.py

class Newsletter(models.Model):
    issue_number = models.IntegerField(null=True)
    title = models.CharField(max_length=200, default="Dynamic Digest")
    summary = models.TextField(null=False, blank=False)
    title_1 = models.CharField(max_length=30, null=False, blank=False)
    image_1 = models.URLField(null=False, blank=False)
    body_1 = models.TextField(null=False, blank=False)

views.py

def write_newsletter(request):
    if request.method == 'POST':
        form = NewsletterForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(list_newsletters())

    form = NewsletterForm()
    return render(request, 'newsletter/edit-newsletter.html', {'form': form})

forms.py

from ckeditor.widgets import CKEditorWidget

class NewsletterForm(ModelForm):
    body_1 = forms.CharField(widget=CKEditorWidget(), required=True)

class Meta:
    model = Newsletter
    fields = ['title',
              'summary',
              'title_1',
              'image_1',
              'body_1'
             ]

labels = {
        'title_1': _('Title'),
        'image_1': _('Image'),
        'body_1': _('Body')
         }

 widgets = {
    "title": forms.TextInput(attrs={'class': 
     'form-control form-title text-center',
     'placeholder': 'Newsletter Header Title'}),
    "summary": forms.Textarea(attrs={'placeholder': 'Newsletter Header Summary'}),
    "title_1": forms.TextInput(attrs={'class': 
    'form-control form-title text-center', 'placeholder': 'Title'}),
    "image_1": forms.URLInput(attrs={'placeholder': 'Image url https://'}),
    }
HTML
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}

Copy of relevant part of request.POST (from views.py print(request.POST))
As you can see a “This field is required.” error is present, but for some reason this is not displayed AND submit is allowed.

<tr>
    <th><label for="id_image_1">Image:</label></th>
    <td><input type="url" name="image_1" value="http://localhost:8000/newsletter/write-newsletter/"
               placeholder="Image url https://" maxlength="200" required id="id_image_1"></td>
</tr>
<tr>
    <th><label for="id_body_1">Body 1:</label></th>
    <td>
        <ul class="errorlist">
            <li>This field is required.</li>
        </ul>
        <div class="django-ckeditor-widget" data-field-id="id_body_1" style="display: inline-block;">
            <textarea cols="40" id="id_body_1" name="body_1" rows="10" required data-processed="0"
                      data-config="{&quot;skin&quot;: etc. etc. }"
                      data-external-plugin-resources="[]" data-id="id_body_1" data-type="ckeditortype"></textarea>
        </div>
    </td>
</tr>

You’re double-defining the body_1 field.

When you define the body_1 field in the form, that overrides the use of the model form-created field in the fields list. This means that in your save method, you’d need to copy the form field to the model field.

Or, you can remove the definition of that field from the form, and specify the widget in the widgets section.

Hi Ken,
Many thanks for your quick reply.
I am unsure how to specify the widget in the widgets section, can you please give me an example based on the code that have have posted?
I really appreciate your help with this.

You already have a widgets section in your form, this just becomes another entry in that dict.

Thanks Ken,
Without making any other changes to my original config I’ve tried changing:
“body_1”: forms.Textarea(attrs={‘placeholder’: ‘Body First Section’}),
To:
“body_1”: forms.Textarea(attrs={‘placeholder’: ‘Body First Section’, ‘widget’: CKEditorWidget}),

But this doesn’t work, as the CKEditor window doesn’t appear, please, do you have any suggestions?

Is that the entry you made in the widgets dict?

Did you remove the form field?

Also:

  • What version of Django are you using?
  • What library are you using that is providing the CKEditorWidget?
  • What version of Python are you using?
    (Have you verified compatibility among all components?)

Thank you Ken, current config as follows:

class NewsletterForm(ModelForm):

class Meta:
    model = Newsletter
    fields = ['title',
              'summary',
              'title_1',
              'image_1',
              'body_1',

widgets = {
        "title": forms.TextInput(
        attrs={'class': 'form-control form-title text-center', 'placeholder': 'Newsletter Header Title'}),
        "summary": forms.Textarea(attrs={'placeholder': 'Newsletter Header Summary'}),
        "title_1": forms.TextInput(attrs={'class': 'form-control form-title text-center', 'placeholder': 'Title'}),
        "image_1": forms.URLInput(attrs={'placeholder': 'Image url https://'}),
        "button_1_name": forms.TextInput(attrs={'placeholder': 'Button name'}),
        #"body_1": forms.Textarea(attrs={'placeholder': 'Body First Section'}),
        "body_1": forms.Textarea(attrs={'placeholder': 'Body First Section', 'widget': CKEditorWidget}),

Python 3.9.4
Django version 3.2.1
django-ckeditor-6.0.0

Have you tried this with passing an instance of the widget rather than the class?

"body_1": forms.Textarea(attrs={'placeholder': 'Body First Section', 'widget': CKEditorWidget()}),

Also, the last update was September, which is pre-Django 3.2. The travis file only shows Python 3.8 and Django 3.1. You might want to try to find some confirmation that this mix of releases will work.

Thanks again, I’ll give that a go and revert.

The page still doesn’t display any “Must Fill Out” prompt when the CK text editor is empty.
So far I have installed Python 3.8.10 and Django 3.1. I have tried using your above suggestions.
I even changed the script to echo exactly the CK documentation suggestion for forms.py, as follows:

class NewsletterForm(ModelForm):
body_1 = forms.CharField(widget=CKEditorWidget())

class Meta:
    model = Newsletter
    fields = '__all__'

and nothing else…

This is not a browser specific issue, because i’ve tried it in webkit, chrome and mozilla, with the same result.

So for clarity:

You are now getting the CK text editor box - the only remaining issue is that a submitted form with no data in it is not failing the validation for the model field body_1?

Or is it that it is failing the validation, but not showing the message on the page?

Hi Ken,

Exactly!
To confirm,

  1. On submit, I get no browser based failure flag for a CKEditor field that has no data, however, the form is allowed to submit but is ultimately not saved, due to it being blocked by the “if form.is_valid():” statement in views.py).
  2. For all other NON CKEditor fields, the browser based failure flag works perfectly.

Thank you for persevering with this!

Ok, so what you’re saying here is that Django is working exactly as it’s supposed to.

Django can only perform validation tests on the data after it has been submitted to the server.

If you have some facility that is performing validation in the browser, that’s all JavaScript. You would need to look at whatever JavaScript library is performing the client-side validation, and see how to integrate that with your CKEditor field.