Использование WTForms, Flask, CKEditor
У меня есть поле в форме с динамическим количеством записей; их можно как добавлять, так и удалять. Настроить это в WTForms было непросто, и я воспользовался решением, обсуждаемым в по этой ссылке. Я не очень хорошо знаком с JS, поэтому мне сложно адаптировать эту функцию для создания CKEditorField вместо обычного StringField/<textarea>.
Приведенный ниже код работает, ничего не «сломано», но RTE не отображается, только обычное текстовое поле. Это заставило меня поверить, что проблема в HTML, а не в файлах Flask, но на всякий случай я включил оба.
То, что я пробовал до сих пор:
(1) Маркер StringField в форме ввода заменен на CKEditorField (Forms.py, ниже).
(2) Добавление «class=ckeditor» в возвращаемую текстовую область и ручной вызов функций замены CKEditor (хотя в документации говорится, что это включается автоматически) (app.js)
(3) Создание и возврат вызовов CKEditor.config в HTML для каждого вновь сгенерированного поля (в документации предлагается использовать это для каждого CKE на странице и в других местах, где я использую CKE ((нединамически создаваемые)) работаем с этим на месте) (app.js)
(4) Замена возврата <textarea> вызовом CKEditor.create (я думаю, что именно здесь и кроется ответ, но он не сработал, и я не могу понять, что еще попробовать) (app.js/ веб-страница.html)
Также отметим, что другое поле формы, планы, успешно работает с редактором.
Формы.py
class Entry(FlaskForm):
# (1)
# bullet = StringField(validators=[validators.DataRequired()])
bullet = CKEditorField(validators=[validators.DataRequired(), validators.Length(max=1000)])
class MyForm(FlaskForm):
entries = FieldList(FormField(Entry), min_entries=0)
plans = CKEditorField('Plans', [validators.InputRequired(), validators.Length(max=1000)])
приложение.js
$(document).ready(function() {
// (2)
// These are redundant but not working
CKEDITOR.replaceClass = 'ckeditor';
CKEDITOR.replaceAll( 'ckeditor' );
var addCount = 1;
$("#addNewField").click(function() {
var newInput = $("#entries");
newInput.append(GetDynamicTextBox("", addCount));
$("#entries").append(newInput);
addCount += 1;
listCKE();
});
// (3)
// CKE Documentation says to include a config instruction
// (in the form of) {{ ckeditor.config(name = "misc_risks") }}
// This fn is my attempt at returning one of those for each added entry to the HTML
// This doesn't work, just returns raw text printed on the page
function listCKE() {
var items = ``;
for (var i=0; i<addCount; i++) {
items += `{{ckeditor.config(name = "subdir`+i.toString()+`")}}`;
}
console.info(items)
document.getElementById("listcke").innerHTML = items;
}
});
function GetDynamicTextBox(value, addCount) {
return '<div>' + 'Entry ' + addCount + ': ' +
// (4)
// Tried replacing <textarea> with ckeditor create func; didn't work
// '{{ckeditor.create(name = "subdir' + addCount + '")}}' +
// (2)
'<textarea class = "ckeditor" name = "subdir' + addCount + '"type = "text" value = "' + value + '"> </textarea> ' +
'<input type = "button" value = "Remove" class = "remove" />' + '</div>' ;
}
$(function () {
$("#addNewField").click(function() {
$("#entries").append(GetDynamicTextBox("", addCount));
});
$("body").on("click", ".remove", function () {
$(this).closest("div").remove();
addCount -= 1;
});
});
веб-страница.html
<form method = "POST" enctype = "multipart/form-data">
{{form.csrf_token}}
<button type = "button" id = "addNewField">Add Entry</button>
{% for subdir in form.subdirs %}
{{ forms.render_field(subdir.name) }}
{% endfor %}
{{ form.entries() }}
{{ form.plans.label }} {{ form.plans() }}
<p><input type = "submit" value = "Submit"></p>
</form>
{{ ckeditor.load(pkg_type = "basic") }}
{{ ckeditor.config(name = "plans") }}
<!-- (4) This was used with the listCKE function in app.js. Didn't work, only returned raw text in {{}} to page -->
<!-- <div id = "listcke"></div> -->



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Механизм шаблонов Flask jinja работает на стороне сервера, а javascript — на клиенте. По этой причине смешивание выражений из обоих миров, как вы пытаетесь сделать во время выполнения кода JavaScript, невозможно.
В следующем примере показан один из способов динамического добавления полей, использующих CKEditor. Здесь для каждого поля устанавливаются уникальные атрибуты name и id, а для связанной метки устанавливается соответствующий атрибут for.
from flask import (
Flask,
render_template,
request,
)
from flask_ckeditor import (
CKEditor,
CKEditorField
)
from flask_wtf import FlaskForm
from wtforms import FieldList, FormField
app = Flask(__name__)
app.secret_key = 'your secret here'
ckeditor = CKEditor(app)
class EntryForm(FlaskForm):
class Meta:
csrf = False
bullet = CKEditorField('Entry')
class MyForm(FlaskForm):
entries = FieldList(FormField(EntryForm), min_entries=0)
@app.route('/', methods=['GET', 'POST'])
def index():
form = MyForm(request.form)
if form.validate_on_submit():
print(form.entries.data)
return render_template('index.html', **locals())
<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<meta name = "viewport" content = "width=device-width, initial-scale=1">
<title>Index</title>
</head>
<body>
<form method = "post">
{{ form.csrf_token }}
<div>
<button type = "button" id = "btn-add">Add</button>
</div>
<div id = "entries">
{% for f in form.entries %}
<div>
{{ f.bullet.label() }}
{{ f.bullet() }}
<button type = "button" class = "btn-remove">Remove</button>
</div>
{% endfor -%}
</div>
<div>
<button type = "submit">Submit</button>
</div>
</form>
{{ ckeditor.load() }}
{# Create a configuration for each CKEditor that already exists. #}
{% for f in form if f.type == CKEditorField -%}
{{ ckeditor.config(name=f.name) }}
{% endfor -%}
<script
src = "https://code.jquery.com/jquery-3.7.1.slim.min.js"
integrity = "sha256-kmHvs0B+OpCW5GVHUNjv9rOmY0IvSIRcf7zGUDTDQM8 = "
crossorigin = "anonymous"></script>
<script>
$(document).ready(() => {
const entriesEl = $('#entries');
$('#btn-add').click(() => {
// If the button to add is clicked...
let ids = ['entries-0-bullet'];
const sel = 'textarea[name$ = "-bullet"]';
const entries = entriesEl.find(sel);
if (entries.length) {
// ... and there are already input fields ...
const lastEntry = entries.last().closest('div');
ids = $.map($(lastEntry).children(sel), function(elem) {
// ... extract the name attribute of the last input field
// and generate a new unique id from it.
const attr = $(elem).attr('name'),
s = attr.replace(/(\w+)-(\d+)-bullet$/, (match, p1, p2) => {
return `${p1}-${parseInt(p2)+1}-bullet`;
});
return s;
});
}
// For each id created a block with the new input field.
// Register a function to remove the block and configure the CKEditor.
$.each(ids, function(index, value) {
const newEntry = $.parseHTML(`<div>
<label for = "${value}">Entry</label>
<textarea class = "ckeditor" id = "${value}" name = "${value}"></textarea>
<button type = "button" class = "btn-remove">Remove</button>
</div>`);
$(newEntry).children('.btn-remove').click(function() {
$(this).closest('div').remove();
})
entriesEl.append(newEntry);
CKEDITOR.replace(value);
});
});
// Register a function to remove fields that already exist.
$('.btn-remove').click(function() {
$(this).closest('div').remove();
});
});
</script>
</body>
</html>