'django-ckeditor modal form does not change data on POST

I have a django project using django-ckeditor. I use HTMX to create a bootstrap modal to show my edit form. It renders correctly (I did add the ckeditor.js files at the end of the body in base.html). If I call my form by going to localhost/1/update, change the RTF value, and click save, it works fine. But rendering it in the modal form and clicking save, the form.has_changed() returns false. If I edit another field and it saves, the value of RTF does NOT change. It seems like the POST does not include the changed value for the CKEditor field.

I've tried to reduce the code below to make it as short as possible.

My model:

class MyModel(models.Model):    
    name = models.CharField(max_length=50)
    description = models.CharField(max_length=150)
    comment = RichTextField(blank=True, null=True)

    def __str__(self):
        return f'{self.name} - {self.description}'

My form:

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = (
            'name',
            'description',
            'comment',
        )

My View

def update_data(request, pk):
    model = MyModel.objects.get(id=pk)
    form = MyForm(request.POST or None, instance=model)

    if request.method == "POST":
        if form.is_valid():
            print(form.has_changed())
            form.save()
            return redirect("detail-form", pk=model.id)

My HTML#1 - I click on the update button to open the modal

{% extends "layouts/base.html" %}

{% load static %}

{% block content %}
<main>
    <div class="section section-md pt-4">
        <div class="container">
            <table class="table">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Description</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody id="myforms">
                    {% for entry in entries %}

                    <tr id="entryform_{{entry.id}}">
                        <td>
                            <h4 class="h5">{{ entry.name }}</h4>
                        </td>
                        <td>
                            <h4 class="h5">{{ entry.description }}</h4>
                        </td>
                        <td style="width: 200px">
                            <button hx-get="{% url 'update-entry' entry.id %}" hx-swap="outerHTML"
                            class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                                Update
                            </button>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>

    <div id="modals-here"></div>
</main>

{% endblock content %}

{% block javascripts %}
<script>
function closeModal() {
    var container = document.getElementById("modals-here")
    var backdrop = document.getElementById("modal-backdrop")
    var modal = document.getElementById("modal")

    modal.classList.remove("show")
    backdrop.classList.remove("show")

    setTimeout(function() {
        container.removeChild(backdrop)
        container.removeChild(modal)
    }, 200)
}
</script>
{% endblock javascripts %}

and then the last html, the model form. Notice that I did add the {{ form.media }}

<div id="modal-backdrop" class="modal-backdrop fade show" style="display:block;">    </div>
<div id="modal" class="modal fade show" tabindex="-1" style="display:block;">
    <div class="modal-dialog modal-dialog-centered">
      <div class="modal-content">
        <div class="modal-body">
            <form method="post">
                {% csrf_token %}
        
                {{ form.media }}
                {{ form.as_p|safe }}
                <button type="submit" hx-post="{% url 'update-data' entry.id %}" hx-target="#entryform_{{entry.id}}" hx-swap="outerHTML"
                    class="btn btn-primary" onclick="closeModal()">
                    Submit
                </button>
            </form>
        </div>
      </div>
    </div>
  </div>

Any advice? My gut feel says it has something to do with the fact that the editor only gets loaded after the page has already loaded. If I recall I had a similar issue years ago with a JS date picker. I can't recall for the life of me how I fixed it then.



Solution 1:[1]

The problem was not with the modal code. HTMX caused the issue. This would always be the issue when using HTMX with RTF editors, and I think this solution would work for all cases. I found a similar issue with tinyMCE with a resolution for tinyMCE here. My implementation with CKEditor is the code as follows:

<div id="modal-backdrop" class="modal-backdrop fade show" 
style="display:block;">    </div>
<div id="modal" class="modal fade show" tabindex="-1" style="display:block;">
    <div class="modal-dialog modal-dialog-centered">
      <div class="modal-content">
        <div class="modal-body">
            <form method="post">
                {% csrf_token %}
    
                {{ form.media }}

                <script>
                    document.body.addEventListener('htmx:configRequest', (event) => {
                        var element = new CKEDITOR.dom.element( document.getElementById( '{{ form.comment.id_for_label }}' ) );
                        event.detail.parameters['{{ form.comment.html_name }}'] = element.getEditor().getData();        
                    })
                </script>
                {{ form.as_p|safe }}
                <button type="submit" hx-post="{% url 'update-data' entry.id %}" hx-target="#entryform_{{entry.id}}" hx-swap="outerHTML"
                    class="btn btn-primary" onclick="closeModal()">
                    Submit
                </button>
            </form>
        </div>
      </div>
    </div>
  </div>

It might not be the best spot to put the script, but it works fine there. This spot also makes it possible to make it more generic using form tags.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Christo Labuschagne