Aarti Joshi
Aarti Joshi

Reputation: 405

How to save HTML tags + data into sqlalchemy?

I am creating a personal blog website with Flask and sqlalchemy. While posting my blogs, I want the blog to be published with well formatted html.

Here is my model for Blogs:

class Blog(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(), index=True)
description = db.Column(db.Text(), index=True)
content = db.Column(db.Text(), index=True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)

likes = db.Column(db.Integer, default=0)
dislikes = db.Column(db.Integer, default=0)

comments = db.relationship('Comment', backref='commented_by', lazy='dynamic')

def __repr__(self):
    return 'Title <>'.format(self.title)

And here is my form for adding blog:

{% extends 'base.html' %} 

{% block content %}
<div class="container">

        
    <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
            <h1 class="code-line text-center" data-line-start="14" data-line-end="15">Add Blog</h1>             
            <br>
        </div>
    </div>
<form action="" method="POST" novalidate>
    {{ form.hidden_tag() }}
    <p>
        {{ form.title.label }}<br>
        {{ form.title(size=30) }}<br>
    </p>
    <p>
        {{ form.description.label }}<br>
        {{ form.description(size=30) }}<br>
    </p>
    <p>
        {{ form.content.label }}<br>
        {{ form.content() }}<br>
    </p>
    <p>
        {{ form.submit() }}
    </p>


</form>

{{ ckeditor.load() }}
{{ ckeditor.config(name='content') }}

{% endblock %}

This is how I am rendering my blog:

{% extends 'base.html' %}
{% block content %}    
<div class="container">

        
    <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
            <h1 class="code-line text-center" data-line-start="14" data-line-end="15">{{ blog.title }}</h1>             
            <br>
            {{ blog.content }}
        </div>
    </div>
</div>
 {% endblock %}

While adding blog, I am using a text editor

enter image description here

But once it has been posted and I render it on view blog page, no html content is being rendered not even linebreaks enter image description here

How can I save html content and tags in my sql database and then render it using jinja template?

Upvotes: 0

Views: 1137

Answers (2)

Gitau Harrison
Gitau Harrison

Reputation: 3517

In the context of markdown, it is actually possible to apply the same format to your database-saved content. You can use a few packages to help you work with HTML in your database.

To begin, let me suggest Stackoverflow QA forms. Notice how it has enabled markdown editing and a nice little preview of the text being written. To enable the preview, you can install the flask-pagedown package in your virtual environment.

(venv)$ pip3 install flask-pagedown

Initialize a pagedown object in your application's instance, say in __init__.py file, or whatever other file you are using.

# __init__.py

from flask import Flask
from flask_pagedown import PageDown

app = Flask(__name__)

pagedown = PageDown(app)

Within your head tags in HTML, add this CDN call whose files you do not need to have in your application.

<!-- base.html -->

{% block head %}
    {{ pagedown.html_head() }}
{% endblock %}

Or this:

<head>
    {{ pagedown.html_head() }}
</head>

If you prefer to use your own JavaScript source files, you can simply include your Converter and Sanitizer files directly in the HTML page instead of calling pagedown.html_head():

<head>
    <script type="text/javascript" src="https://mycdn/path/to/converter.min.js"></script>
    <script type="text/javascript" src="https://mycdn/path/to/sanitizer.min.js"></script>
</head>

Now, simply update your forms to use PageDownField:

# forms.py

from flask_pagedown.fields import PageDownField

class Post(FlaskForm):
    post = PageDownField('Comment', validators=[DataRequired()])

Or this:

<form method="POST">
    {{ form.pagedown(rows=10) }}
</form>

That's it! You should be able to have a client-side post preview right below the form.

Handling Rich Text in the Server

When the post request is made, then only raw markdown will be sent to the database and the preview will be discarded. It is a security risk to send HTML content to your database. An attacker can easily construct HTML sequences which don't match the markdown source and submit them, hence the reason why only markdown text is submitted. Once in the server, that text can be converted back to HTML using a Python markdown-to-html convertor. There are two packages that you can make use of. Install then in your virtual environment as seen below:

(venv)$ pip3 install markdown bleach

bleach is used to sanitize the HTML you want converted to allow for a set of tags.

At this point, the next logical step would be to cache your content field content while in the database. This is done by adding a new field, let us say content_html, in your database specifically for this cached content. It is best to leave your content field as it is in case you would want to use it.

# models.py

class Blog(db.Model):
    content = db.Column(db.String(140))
    content_html = db.Column(db.String(140))

    @staticmethod
    def on_changed_body(target, value, oldvalue, initiator):
        allowed_tags = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code',
        'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul',
        'h1', 'h2', 'h3', 'p']
        target.content_html = bleach.linkify(bleach.clean(
        markdown(value, output_format='html'),
        tags=allowed_tags, strip=True))

    def __repr__(self):
        return f'Title {self.title}'

db.event.listen(Blog.content, 'set', Blog.on_changed_body)

The on_changed_body() function is registered as a listener of SQLAlchemy’s “set” event for body , which means that it will be automatically invoked whenever the body field is set to a new value. The handler function renders the HTML version of the content and stores it in content_html , effectively making the conversion of the Markdown text to HTML fully automatic.

The actual conversion is done in 3 steps:

  • markdown() function does an initial conversion to HTML. The result is passed to clean() function with a list of approved HTML tags
  • clean() function removes any tags that are not in the whitelist.
  • linkify() function from bleach converts any URLs written in plain text into proper <a> links. Automatic link generation is not officially in the Markdown specification, but is a very convenient feature. On the client side, PageDown supports this feature as an optional extension, so linkify() matches that functionality on the server.

In your template, where you want to post your content you can add a condition such as:

{% for blog in blogs %}             
     {% if blog.content_html %}
         {{ blog.content_html | safe }}
     {% else %}
         {{ blog.content }}     
     {% endif %}   
{% endfor %}

The | safe suffix when rendering the HTML body is there to tell Jinja2 not to escape the HTML elements. Jinja2 escapes all template variables by default as a security measure, but the Markdown-generated HTML was generated by the server, so it is safe to render directly as HTML.

Upvotes: 0

Mustapha Khial
Mustapha Khial

Reputation: 132

first, what is wrong:
the text you get from the text field in the form is not the same thing as HTML that renders it, what you are getting is the text.
in case you want to get the HTML generated inthat form, you should integrate a rich text editor, like quilljs.com, or tiny.cloud in your template, that will have a field that you can use, to grab the HTML it generated, and it will also allow you to create nice looking blog articles.
if you do not want this either, to get html from that form, writing HTML directly in that form will give you what you want.

Upvotes: 1

Related Questions