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.
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'), 500Overall, 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
