基于Flask的自定义网站设计与实现(代码全文+讲解V1.0)

Flask 是一个用 Python 编写的轻量级 Web 应用框架。它基于 Werkzeug WSGI 工具包和 Jinja2 模板引擎,设计上强调简洁和可扩展性。对于python web方向的开发人员来说,这是一个必会的基本方法。

【注1】开源代码见文章末尾,项目效果见:

        域名还没搞好...,准确的是说一个下来好贵啊。能不能只卖域名和dns...

【注2】本次项目使用pycharm2023和python3.9进行开发(本地),相关解释器建议选择接近版本。如有疑问请评论区咨询,我若看见会在第一时间回复。

【注3】主要参考:

(1)Flask基本框架参考:Flask实现个人博客系统(附源码)_flask个人博客-CSDN博客

(2)服务器部署参考:新手如何使用腾讯云服务器部署Flask项目_腾讯云服务器部署flask项目csrf防御-CSDN博客

后续版本更新超链接:

【更新中,尽情期待!】

0.  项目介绍

本项目使用flask设计了一个深度学习项目平台,用来实现一个展示各种关于python(主要是深度学习方向)的毕设(课设/项目等)。 本项目借鉴于开源博客项目,由于项目具体需求,对部分代码进行了修改。按照二次开发原则,同样开源在csdn和GitHub中。由于本项目纯属自娱自乐,故不定期更新...(当然在首次更新时会有一个较为完整的东西,后续更新见版本号或其它博客)

最新版项目首页展示

1.  从零开始建立项目

与以往的python深度学习项目不同,在pycharm中提供了flask的基本框架,因此选择项目模板进行建立。(这样会自动安装好一些基本的包)

图1  创建项目

 在自动生成之后,一般会得到static用于存放静态文件、templates用于放置html静态模板、和一个运行程序app。在本章中,我们首先对这三个做一个基本的编写,来实现一个简易的网页。

1.1  简易前端设计

首先,在templates文件中创建一个base.html。这个文件是编写页面大致的框架,其他模块直接继承使用即可。这部分保留了开源老哥的原始代码,增加了一些注释。但是如果你是直接运行这个文件。你会发现在网页端是一个排版较为混乱的情况,这是因为没有配置相应的css和javaScript文件。

<!DOCTYPE html>
<html lang="en">

<!--头部信息,确保显示所有字符 -->
<head>
    <meta charset="UTF-8">

    <!--声明一个块,允许子模板覆盖该块的内容,以便不同页面可以设置不同的标题。 -->
    <title>
    {% block title %}
        {# 其他页面可以重写标题 #}
    {% endblock %}
    </title>

    <!--引入外部CSS文件和JavaScript文件,用于页面样式和交互效果。-->
    <link rel="stylesheet" href="/static/Navigation_bar/css/layui.css">
    <link rel="stylesheet" href="/static/css/base.css">
    <script src="/static/js/jquery.js"></script>
    <script src="/static/Navigation_bar/layui.js"></script>

    <!--声明一个CSS块,子模板可以添加额外的CSS样式。-->
    {% block css %}
    {% endblock %}
</head>

<!-- 主体部分,包括一个背景元素和一个导航栏-->
<body>
<div id="bg"></div>
<ul class="layui-nav" lay-filter="">

    <!--导航栏部分,导航栏包含多个链接,根据用户是否登录显示不同的内容-->
    <li class="layui-nav-item"><a href="/">在线博客平台</a></li>
    {% if username %}
        <li class="layui-nav-item{% block updatepwd_class %}{% endblock %}"><a href="/updatePwd">修改密码</a></li>
    {% endif %}
    <li class="layui-nav-item{% block blog_class %}{% endblock %}"><a href="/blog/blogAll">博客</a></li>
    <li class="layui-nav-item{% block about_class %}{% endblock %}"><a href="/about">关于</a></li>

    <!--如果用户已登录,显示用户菜单和写博客的选项;如果未登录,显示注册和登录选项-->
    {% if username %}
        <li class="layui-nav-item" style="float: right; margin-right: 30px;">
            <a href="javascript:;">{{ name }}</a>
            <dl class="layui-nav-child">
                <dd><a href="/blog/myBlog">我的博客</a></dd>
                <dd><a href="/blog/myComment">我的评论</a></dd>
                <dd><a href="/logout">注销</a></dd>
            </dl>
        </li>
        <li class="layui-nav-item{% block write_class %}{% endblock %}" style="float: right"><a href="/blog/writeBlog">写博客</a></li>
    {% else %}
        <li class="layui-nav-item{% block register_class %}{% endblock %}" style="float: right"><a href="/register">注册</a></li>
        <li class="layui-nav-item{% block login_class %}{% endblock %}" style="float: right"><a href="/login">登录</a></li>
    {% endif %}
</ul>

<div class="content">
    {% block content %}
        {# 其他页面内容 #}
    {% endblock %}
</div>

<script>
layui.use('element', function(){
    var element = layui.element;
});
</script>
</body>
</html>

【注4】项目中所提供的jquery.js。这个文件是一个固定格式的库文件,是由 jQuery 项目团队编写和维护的。jQuery 是一个广泛使用的开源 JavaScript 库,其目的是简化 HTML 文档遍历、事件处理、动画以及 Ajax 交互。

1.2  简易flask运行

一个简易的flask界面运行,只需要:

  1. 用当前脚本名称实例化Flask对象。
  2. 创建一个url请求地址到python运行函数的一个映射。
from flask import Flask, render_template

# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)


# 程序实例需要知道每个url请求所对应的运行代码是谁,所以程序中必须要创建一个url请求地址到python运行函数的一个映射。
@app.route('/')
def home():
    return render_template('base.html')


if __name__ == '__main__':
    app.run(debug=True)
图2  简易flask平台

1.3  网站部署

1.3.1  服务器搭建

现有的服务器有很多,建议使用阿里或者腾讯的服务器,有学生优惠。本篇文章将基于腾讯服务器展示。

图3  腾讯服务器

上传项目,点击图3的登录按钮,使用如下图4所示的按钮上传项目:

图4  Linux服务器上传

1.3.2  服务器环境配置

查看python版本

which python
python3 --version

【注5】一般来说,腾讯云服务器中的python3 版本是3.6.8 

安装Flask:

pip3 install flask --user

1.3.3  项目微调与部署

首先我们要进入文件管理器中修改,app.py文件。如下图所示,点击文件管理器的app.py那一栏就可进入编辑。

图5  修改app.py文件

修改端口后,运行程序:

export PYTHONPATH=$PYTHONPATH:/home/lighthouse/.local/lib/python3.6/site-packages
python3 ./DLM_test/app.py
图6  运行后部分截图

到这一步,输入你的公网网址加端口就可以进入网站了。

2.  登录、注册与注销功能

在讲述登录与注册功能之前,要引入一个关于蓝图(Blueprint)的概念。 蓝图是一种组织 Flask 应用程序的方式,它允许你将应用程序分成模块化的组件。这对于管理大型应用程序非常有用,因为你可以将应用分成更小、更可复用的部分。其实也就是在常规项目中,将可重复使用的函数都分类迁移到其它文件中,方便进行不同模块的修改。

具体来说,首先在原始目录下创建一个view文件夹用于存放蓝图文件,之后修改app.py文件如下所示:

from flask import Flask, render_template
from view.main import *

# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)

# 注册蓝图
app.register_blueprint(index)


if __name__ == '__main__':
    app.run(debug=True)

这个代码运行之后就是图2的结果,可以看出在主函数中进行了精简。本章将在这个main文件中继续定义登录和注册函数。

2.1  登录设计

2.1.1  登录窗口设计

登录窗口的设计基于login.html文件和register.css文件,原始代码在开源代码的基础上进行修改,以下是这部分的修改(登录框透明度和字体)。

.register{
    width: 440px; /* 设置元素的宽度为 440 像素 */
    padding: 20px; /* 设置元素内部的填充(内边距)为 20 像素 */
    margin: 20vh auto; /* 上下外边距为视口高度的 20%,左右外边距自动(使元素水平居中) */
    /*background: white; !* 设置元素的背景颜色为白色 *!*/
    border-radius: 15px; /* 设置元素的边框圆角半径为 15 像素,使边框圆滑 */
    box-shadow: -2px -2px 25px #fff; /* 设置元素的盒子阴影,向左上方偏移 2 像素,模糊半径为 25 像素,阴影颜色为白色 */
    background-color: rgba(255, 255, 255, 0.8); /* 设置透明度为0.8 */
    /*padding: 20px;*/
    /*border-radius: 10px; !* 添加圆角效果,可选 *!*/
    /*box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); !* 添加阴影效果,可选 *!*/
}

.register h1 {
    font-size: 2em; /* 设置标题字体大小 */
}

.register .layui-form-label {
    font-size: 1.2em; /* 设置标签字体大小 */
}

.register .layui-input {
    font-size: 1.2em; /* 设置输入框字体大小 */
}

.register .layui-btn {
    font-size: 1.2em; /* 设置按钮字体大小 */
}
.tip{
    font-size:16px;
    color: red;
    text-align: center;
    margin: 10px;
    margin-top: -10px;
}
.layui-form-item {
    padding-right: 60px;
    margin-bottom: 30px;
}
h1{
    position: relative;
    text-align: center;
    top: -40px;
    /*font-size: 2em; !* 设置标题字体大小 *!*/
}

运行改界面的效果图如下图7所示:

图7  登录页面可视化

2.1.2  链接数据库

在上述的介绍种,我们完成了html的设计和编写。但是对于一个登录功能来说,它必须要链接数据库才可行。本次使用的是mysql数据库,在此节中已本地数据库为例。在下面代码中,实现了一个从网页获取登录名和密码的操作,并使用判断语句进行判断,如果判别成功将会进入到登录后的账号模式。

# 登录请求
@index.route('/login', methods=['POST', 'GET'])
def login():
    # 如果请求方法是 GET,函数会渲染并返回 login.html 模板页面,通常用于显示登录表单。
    if request.method == 'GET':
        return render_template('login.html')

    # 如果请求方法是 POST,函数会从表单数据中获取 username 和 password。
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')

        user = User.query.filter(User.username == username).first();

        if (user is not None) and (check_password_hash(user.password, password)):
            session['username'] = user.username
            session.permanent = True
            # 正确登录后的界面
            return redirect(url_for('index.home'))
        else:
            flash("账号或密码错误")
            return render_template('login.html');

在模板作者的原文中,给出了User函数这一定义。通过使用此函数来进行数据库的初始构建,下面是其实现代码:

from app import app
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash

# 实例化
db = SQLAlchemy(app);


class User(db.Model):
    # 设置表名
    __tablename__ = 'blog_login_user';
    # id,主键并自动递增
    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);

【注5】这里的表名是你自己的数据库。

2.1.3  权限增加

与博客不同,本项目只给“管理员”开放编写项目的权限。因此在前后端需要增加对应的筛选语句,首先在前端方面,在原始的“写博客”/“项目发布”出增加一个session判断语句:

        <!--发布权限 -->
        {% if session['is_special_user'] %}
            <li class="layui-nav-item{% block write_class %}{% endblock %}" style="float: right"><a href="/blog/writeBlog">项目发布</a></li>
        {% endif %}

其次,分别修改User函数login函数

        if (user is not None) and (check_password_hash(user.password, password)):
            session['username'] = user.username
            session['is_special_user'] = user.is_special_user  # 将权限信息存储到会话中,这里对于非管理员的其它用户不给予发布项目的权力。
            session.permanent = True
            # 正确登录后的界面
            return redirect(url_for('index.home'))

对于User函数,除了进行如下的修改之外,还需要在mysql中更新数据表: 

class User(db.Model):
    # 设置表名
    __tablename__ = 'blog_login_user';
    # id,主键并自动递增
    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))
    # 权限字段
    is_special_user = db.Column(db.Boolean, default=False)

    # 设置只可写入,对密码进行加密
    def password_hash(self, password):
        self.password = generate_password_hash(password);

也就是手动添加权限:

UPDATE blog_login_user SET is_special_user = TRUE WHERE username = '具有权限的用户名';

对于更新后的数据表,可以参照下图所示,其中1代表具有发布权限的账号:
 

图8  权限更新可视化

2.2  注册设计

注册代码没有什么更新的,在上述完成权限分类后,会自动默认之后注册的都没有使用项目发布的权限。如果后续想要小范围增加权限,只需在MySQL中更新即可。

@index.route('/register', methods=['POST', 'GET'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    if request.method == 'POST':
        name = request.form.get('name')
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter(User.username == username).first();
        if user is not None:
            flash("该用户名已存在")
            return render_template('register.html')
        else:
            user = User(username=username, name=name)
            # 调用password_hash对密码加密
            user.password_hash(password)
            db.session.add(user)
            db.session.commit();
            flash("注册成功!")
            return render_template('register.html')

2.3  注销(登出)设计

同样的,直接使用原有的登出部分:

# 退出账号,如果不退出的话,在这个浏览器中会保存
@index.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index.home'))

2.4  服务器更新

在更新上述代码后,在服务器上直接运行:

python3 ./DLM_test/app.py

会报如下图所示的错误:

图9  服务器端报错

这里是循环的问题,有的时候在本地体现不出来,可以参照下述代码对app.py和database.py文件进行修改:

from flask import *

from database import config

# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)
app.config.from_object(config)
app.secret_key = "flaskblog"

from database.database import init_db
# 初始化数据库
init_db(app)


# 注册蓝图,为了打破循环,必须在这里进行导包
def register_blueprints(app):
    from view.main import index
    from view.blog import blog
    app.register_blueprint(index)
    app.register_blueprint(blog)


register_blueprints(app)

# from database.database import *
from database.database import User

# 后续一样
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash
# from app import app

# 实例化
db = SQLAlchemy();


def init_db(app):
    db.init_app(app)


class User(db.Model):
    # 设置表名
    __tablename__ = 'blog_login_user';
    # id,主键并自动递增
    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))
    # 权限字段
    is_special_user = db.Column(db.Boolean, default=False)

    # 设置只可写入,对密码进行加密
    def password_hash(self, password):
        self.password = generate_password_hash(password);

2.4.1  在服务器上配置mysql

一般来说,在初始的腾讯云服务器中是没有mysql的,那么你就需要自己配置一个。输入下面的安装语句:

# 安装仓库
sudo yum install mysql

# 安装服务器
sudo yum install mysql-server

在连续点击两个y之后,使用如下语句进行激活mysql,并使用第三个语句进行测试。

sudo systemctl start mysqld

sudo systemctl enable mysqld

sudo systemctl status mysqld

 正常运行的样子应该如下图10所示:

图10  服务器mysql状态

在了解mysql状态被激活之后,现在开始尝试在服务器中登录,正确登录可以得到如下11所示的图。

mysql -u root

图11 正确登录mysql

后面就比较简单了,直接复制上述的数据库建立表就可以。

【注6】 这里在服务器上有个脑壳痛的问题,那就是python3.6所对应的哈希表加密没有script的,所以你最好手动修改管理员的密码,使用database中的测试文件。至于常规用户的注册等则无需修改,在服务器生成的里面注册就行。

3.  功能设计与实现

3.1  项目发布相关

项目发布部分则是完全借用的flask开源博客框架部分,只不过在界面上改了一个名字。有所区别的是,在使用2.1.3的权限增加后,只有管理员用户才可以使用此功能。这里就不作累述,如果后续有更新此部分,将展示更新后的代码。

【此部分,后续持续更新中...】

3.2  用户使用相关(基于框架的新增与删减)

对于用户的使用部分,我们将页面修改为这个样子:

图12  用户界面V1.0

 首页、项目一栏不用多说。就是展示界面和查看已经发布的项目,在这里主要对个人中心加以描述。在V1.0版本中,这里包括着个人常规信息和修改密码等功能。

这里主要增加两个部分,一个是在html中增加下面的代码:

{% extends "base.html" %}
{% block title %}个人中心{% endblock %}
{% block content %}
<div class="user-info" style="margin-top: 50px;">
    <form class="layui-form" action="/blog/update_myself" method="post" enctype="multipart/form-data">
        <div class="layui-form-item">
            <div class="layui-input-block" style="text-align: center;">
                {% if user.photo %}
                    <img src="{{ url_for('static', filename=user.photo) }}" alt="用户头像" style="max-width: 150px; border-radius: 50%;" id="user-avatar">
                {% else %}
                    <img src="{{ url_for('static', filename='./img/boy_first.png') }}" alt="默认头像" style="max-width: 150px; border-radius: 50%;" id="user-avatar">
                {% endif %}
                <input type="file" name="photo" class="layui-input" style="display: none;" id="photo-input">
            </div>
        </div>
        <div class="layui-form-item" style="text-align: left;">
            <label class="layui-form-label" style="text-align: left;">用户昵称</label>
            <div class="layui-input-block" style="margin-left: 110px;">
                <input type="text" name="name" required lay-verify="required" placeholder="请输入昵称:" class="layui-input" value="{{ user.nickname }}" readonly>
            </div>
        </div>
        <div class="layui-form-item" style="text-align: left;">
            <label class="layui-form-label" style="text-align: left;">登录名</label>
            <div class="layui-input-block" style="margin-left: 110px;">
                <input type="text" name="username" required lay-verify="required" class="layui-input" value="{{ user.username }}" readonly>
            </div>
        </div>
        <div class="layui-form-item" style="text-align: left;">
            <label class="layui-form-label" style="text-align: left;">密码</label>
            <div class="layui-input-block" style="margin-left: 110px;">
                <input type="password" name="password" placeholder="请输入新密码:" class="layui-input" readonly>
            </div>
        </div>
        <div class="layui-form-item">
            <div class="layui-input-block">
                <button type="button" class="layui-btn" id="edit-btn">修改</button>
                <button type="submit" class="layui-btn" id="save-btn" style="display: none;">保存</button>
                <button type="button" class="layui-btn layui-btn-primary" id="cancel-btn" style="display: none;">取消</button>
            </div>
        </div>
    </form>
</div>

<script>
document.getElementById('edit-btn').addEventListener('click', function() {
    var inputs = document.querySelectorAll('input[name="name"], input[name="password"]');
    inputs.forEach(function(input) {
        input.removeAttribute('readonly');
    });
    document.getElementById('user-avatar').addEventListener('click', function() {
        document.getElementById('photo-input').click();
    });
    document.getElementById('edit-btn').style.display = 'none';
    document.getElementById('save-btn').style.display = 'inline-block';
    document.getElementById('cancel-btn').style.display = 'inline-block';
});

document.getElementById('cancel-btn').addEventListener('click', function() {
    var inputs = document.querySelectorAll('input[name="name"], input[name="password"]');
    inputs.forEach(function(input) {
        input.setAttribute('readonly', 'readonly');
        input.value = input.defaultValue; // 还原初始值
    });
    document.getElementById('photo-input').style.display = 'none';
    document.getElementById('edit-btn').style.display = 'inline-block';
    document.getElementById('save-btn').style.display = 'none';
    document.getElementById('cancel-btn').style.display = 'none';
});
</script>
{% endblock %}

另一个是在blog文件中增加如下部分,当然,你也可以选择单开一个蓝图。

# 个人中心页面
@blog.route('/myself')
@login_limit
def myself():
    username = session.get("username")
    user = User.query.filter(User.username == username).first()
    # user = get_user_info(username)
    return render_template("myself.html", user=user)


# 用户信息更新
@blog.route('/update_myself', methods=['POST'])
@login_limit
def update_myself():
    username = session.get("username")
    name = request.form.get("name")
    password = request.form.get("password")
    photo = request.files.get("photo")

    user_data = {"name": name}
    if password:
        user_data["password"] = generate_password_hash(password)
    if photo:
        photo_path = f'static/uploads/{username}_photo.jpg'
        photo.save(photo_path)
        user_data["photo"] = photo_path

    update_user_info(username, user_data)
    return redirect(url_for('blog.myself'))


def update_user_info(username, user_data):
    user = User.query.filter(User.username == username).first()
    if user:
        if "name" in user_data:
            user.name = user_data["name"]
            # print(user.name)
        if "password" in user_data:
            user.password = user_data["password"]
        # 更换头像的功能在本版本还未更新!
        # if "photo" in user_data:
        #     user.photo = user_data["photo"]
        db.session.commit()

 效果图如下所示:

3.3  服务器更新

3.1.1  服务器端项目发布BUG

(1)图片上传失败

图xx  服务器端图片上传失败

这里有两个需要修改的地方,一个是将上传函数修改为如下所示的:

# 上传图片
@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];

        # 生成一个uuid作为文件名
        fileName = str(uuid.uuid4()) + "." + ext;
        filePath = os.path.join("/home/lighthouse/DLM_TEST/static/uploadImg/", fileName);
        file.save(filePath)

        return {
            'success': 1,
            'message': '上传成功!',
            'url': url_for('static', filename='uploadImg/' + fileName)  # 返回相对路径
        }
    except Exception as e:
        logging.error(f"上传失败: {e}", exc_info=True)  # 记录详细的错误信息
        return {
            'success': 0,
            'message': '上传失败'
        }

另一个是将url改为服务器的网址,不然图片上传成果了也显示不出来。 

4.  项目地址

本项目全部开源,地址为:damoshishen/Deep_Learning_Mall (github.com) 该地址持续更新,同时扩展部分也会持续更新。

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物理系的计算机选手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值