TinyMCE and Flask
Posted on
by Kevin FoongIn this post I will run through process of setting up the TinyMCE editor with Flask. TinyMCE is a great WYSIWYG editor and suitable for things like blog posts. It also enables you to upload images directly into your text.
Follow the instructions below to set up TinyMCE as a local download.
- In Flask I create your forms as per normal using Flask-WTF. Note that the
post
field is defined as a TextAreaField. Later we will be using the TinyMCE editor to replace this field.
class PostForm(FlaskForm):
heading = StringField('Title', validators=[InputRequired(), Length(max=100)])
post = TextAreaField('Write something')
tags = StringField('Tags')
submit = SubmitField('Submit')
- Now download TinyMCE (see https://www.tiny.cloud/docs/general-configuration-guide/advanced-install/#sdkinstall)
and unzip the contents into a web accessible location. I downloaded mine into a folder named "tinymce" under the static folder. - Create a route which will look something like this.
@app.route('/post')
def post():
form = PostForm()
return render_template('post/post.html',form=form)
- In the "post.html" template which contains the form, you will need to include the TinyMCE script and initialise it (see below). As you can see there are a lot of options available. The important one to take note of for now is
selector
which will point to the id of the TextAreaField we defined in step 1. We know the id will be "#post" because the id will be the same as the name of the field. Flask automatically sets the id of each element to the name of the field you have defined.
......
<div class="field">
{{ form.heading.label(class_='label') }}
<p class="control">
{{ form.heading(class_="input") }}
</p>
</div>
<div class="field">
{{ form.post.label(class_='label') }}
<p class="control">
{{ form.post(rows=16) }}
</p>
</div>
<div class="field">
<div class="control">
{{ form.submit(class_="button is-link") }}
</div>
</div>
<script type="text/javascript" src="{{ url_for('static', filename='tinymce/tinymce.min.js') }}"></script>
<script type="text/javascript">
tinymce.init({
selector: '#post',
plugins: [
'advlist autolink link image imagetools lists charmap print preview hr anchor pagebreak spellchecker',
'searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking',
'save table contextmenu directionality template paste textcolor codesample'
],
imagetools_toolbar: "rotateleft rotateright | flipv fliph | editimage imageoptions",
toolbar: 'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image | print preview media fullpage | forecolor backcolor emoticons | codesample',
images_upload_url: '{{ url_for('post.imageuploader') }}',
automatic_uploads: true,
images_reuse_filename: false,
images_upload_base_path: '/static/uploads',
codesample_languages: [
{ text: 'HTML/XML', value: 'markup' },
{ text: 'JavaScript', value: 'javascript' },
{ text: 'CSS', value: 'css' },
{ text: 'Processing', value: 'processing' },
{ text: 'Python', value: 'python' }
],
width: "100%",
});
</script>
- At this stage when you run your app, the TinyMCE editor should appear in place of the TextAreaField. You will now be able to use the TinyMCE editor and all its features. Then when you are finished editing and and when you click submit, the HTML that you have entered will be automatically set back to the original TextAreaField. You can then continue to write that data into the database as normal. Flask does not even need know that you were using TinyMCE.
- You can also upload images directly into TinyMCE.
images_upload_url
was defined in step 3 which refers to functionimageuploader
. We now need to define this function. This function basically saves the image on the server and appends the image link back into the TinyMCE editor.
@app.route('/imageuploader', methods=['POST'])
@login_required
def imageuploader():
file = request.files.get('file')
if file:
filename = file.filename.lower()
if ext in ['jpg', 'gif', 'png', 'jpeg']:
img_fullpath = os.path.join(app.config['UPLOADED_PATH'], filename)
file.save(img_fullpath)
return jsonify({'location' : filename})
# fail, image did not upload
output = make_response(404)
output.headers['Error'] = 'Image failed to upload'
return output
That's it!
Some additional ideas includes saving a thumbnail version of the image and saving the image metadata (such as file name, width x height, file size) into a database. Both of these can be accomplished with the help of the Pillow library. I may write another tutorial on this in another post. Meanwhile if you have any comments do let me know.
You can see the full source code on Github
Update - I have now also written part 2 here.