Flask Blog Platform Development Tutorial

This article provides a step‑by‑step tutorial for building a full‑stack online blog platform with Flask, MySQL, Layui, and a Markdown editor, covering environment setup, database schema, model definitions, template inheritance, user authentication, CRUD operations for blogs, comment handling, image upload, and custom error pages.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Flask Blog Platform Development Tutorial

This guide walks through creating an online blog platform using Flask (Python 3.7) and MySQL 5.5, with the front‑end built on Layui and a Markdown editor for rich text content.

Project Environment

Development tools: PyCharm, Python 3.7, MySQL 5.5.

Key Features

User registration and login

Write, edit, and delete blogs (Markdown support)

View blog list and detailed pages

Comment on blogs

Change password (Werkzeug password hashing)

Image upload for Markdown editor

Personal blog and comment management

Custom 404/500 error pages

Database Design

Three tables are defined: tb_user, blog, and comment, with one‑to‑many relationships (user ↔ blog, user ↔ comment, blog ↔ comment).

class User(db.Model):
    __tablename__ = 'tb_user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(64), unique=True)
    password = db.Column(db.String(256), nullable=True)
    name = db.Column(db.String(64))
    def password_hash(self, password):
        self.password = generate_password_hash(password)

class Blog(db.Model):
    __tablename__ = 'blog'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(128))
    text = db.Column(db.TEXT)
    create_time = db.Column(db.String(64))
    user_id = db.Column(db.Integer, db.ForeignKey('tb_user.id'))
    user = db.relationship('User', backref='user')

class Comment(db.Model):
    __tablename__ = 'comment'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    text = db.Column(db.String(256))
    create_time = db.Column(db.String(64))
    blog_id = db.Column(db.Integer, db.ForeignKey('blog.id'))
    user_id = db.Column(db.Integer, db.ForeignKey('tb_user.id'))
    blog = db.relationship('Blog', backref='blog')
    user = db.relationship('User', backref='use')

Template Structure

All pages inherit from base.html, which defines the navigation bar and common CSS/JS includes. Example of base.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="/static/layui/css/layui.css">
    <link rel="stylesheet" href="/static/css/base.css">
    <script src="/static/js/jquery.js"></script>
    <script src="/static/layui/layui.js"></script>
    {% block css %}{% endblock %}
</head>
<body>
    ... navigation using {% if username %} ... {% endif %} ...
    <div class="content">{% block content %}{% endblock %}</div>
    <script>layui.use('element', function(){ var element = layui.element; });</script>
</body>
</html>

User Authentication

Login view handles GET (render form) and POST (validate credentials, store username in session). Passwords are verified with check_password_hash.

@index.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    username = request.form.get('username')
    password = request.form.get('password')
    user = User.query.filter(User.username == username).first()
    if user and check_password_hash(user.password, password):
        session['username'] = user.username
        session.permanent = True
        return redirect(url_for('index.hello'))
    flash('账号或密码错误')
    return render_template('login.html')

Registration uses the same template structure with a shared CSS file.

Password Change

The /updatePwd route verifies the old password, checks that the two new passwords match, then updates the stored hash.

@index.route('/updatePwd', methods=['GET', 'POST'])
@login_limit
def update():
    if request.method == 'GET':
        return render_template('updatePwd.html')
    lodPwd = request.form.get('lodPwd')
    newPwd1 = request.form.get('newPwd1')
    newPwd2 = request.form.get('newPwd2')
    username = session.get('username')
    user = User.query.filter(User.username == username).first()
    if check_password_hash(user.password, lodPwd):
        if newPwd1 != newPwd2:
            flash('两次新密码不一致!')
            return render_template('updatePwd.html')
        user.password_hash(newPwd2)
        db.session.commit()
        flash('修改成功!')
        return render_template('updatePwd.html')
    flash('原密码错误!')
    return render_template('updatePwd.html')

Blog CRUD

Writing a blog saves title, Markdown content, and timestamp. Example:

@blog.route('/writeBlog', methods=['GET', 'POST'])
@login_limit
def writeblog():
    if request.method == 'GET':
        return render_template('writeBlog.html')
    title = request.form.get('title')
    text = request.form.get('text')
    username = session.get('username')
    create_time = time.strftime('%Y-%m-%d %H:%M:%S')
    user = User.query.filter(User.username == username).first()
    blog = Blog(title=title, text=text, create_time=create_time, user_id=user.id)
    db.session.add(blog)
    db.session.commit()
    blog = Blog.query.filter(Blog.create_time == create_time).first()
    return render_template('blogSuccess.html', title=title, id=blog.id)

Blog list page queries blogs ordered by create_time.desc() and displays title, author, and timestamp.

@blog.route('/blogAll')
def blogAll():
    blogList = Blog.query.order_by(Blog.create_time.desc()).all()
    return render_template('blogAll.html', blogList=blogList)

Blog detail page converts stored Markdown to HTML using editormd.markdownToHTML and shows a comment form.

Comment System

Comments are posted via AJAX to /blog/comment, storing text, blog ID, user ID, and timestamp.

@blog.route('/comment', methods=['POST'])
@login_limit
def comment():
    text = request.values.get('text')
    blogId = request.values.get('blogId')
    username = session.get('username')
    create_time = time.strftime('%Y-%m-%d %H:%M:%S')
    user = User.query.filter(User.username == username).first()
    comment = Comment(text=text, create_time=create_time, blog_id=blogId, user_id=user.id)
    db.session.add(comment)
    db.session.commit()
    return {'success': True, 'message': '评论成功!'}

Image upload for the Markdown editor saves the file with a UUID filename and returns JSON with the file URL.

@blog.route('/imgUpload', methods=['POST'])
@login_limit
def imgUpload():
    try:
        file = request.files.get('editormd-image-file')
        fname = secure_filename(file.filename)
        ext = fname.rsplit('.', 1)[-1]
        fileName = str(uuid.uuid4()) + '.' + ext
        filePath = os.path.join('static/uploadImg/', fileName)
        file.save(filePath)
        return {'success': 1, 'message': '上传成功!', 'url': '/' + filePath}
    except Exception:
        return {'success': 0, 'message': '上传失败'}

Personal Blog Management

Authenticated users can view, edit, and delete their own blogs via /myBlog, /update/<id>, and /delete/<id> routes. Deletion returns JSON and the front‑end removes the DOM element.

@blog.route('/delete/<id>')
@login_limit
def delete(id):
    blog = Blog.query.filter(Blog.id == id).first()
    db.session.delete(blog)
    db.session.commit()
    return {'state': True, 'msg': '删除成功!'}

Comment Management

Users can list all their comments at /myComment and delete individual comments via /deleteCom/<id>.

@blog.route('/deleteCom/<id>')
def deleteCom(id):
    com = Comment.query.filter(Comment.id == id).first()
    db.session.delete(com)
    db.session.commit()
    return {'state': True, 'msg': '删除成功!'}

Error Handling

Custom 404 and 500 handlers render a simple error page with a background GIF and a link back to the home page.

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template('404.html'), 500

Overall, the article demonstrates a complete Flask‑based blog system, integrating database modeling, Jinja2 templating, user session management, Markdown content handling, file uploads, and graceful error pages.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

MySQLFlaskCRUDBlog
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.