【Python Onramp】6.一篇文章了解web开发要点:用Python开发简易的网页端成绩查询系统

系列文章目录

【Python Onramp】 0. 卷首语

上一篇:【Python Onramp】5.利用Pyecharts进行可视化:综合应用
下一篇:【Python Onramp】7. web端可视化:北京地铁数据统计分析实例以及简易Echarts绘图

项目描述

这个项目中你将学习简单的网站开发逻辑,前端用HTML+JavaScript,后端使用Python的Flask框架。
代码仓库见:https://github.com/Honour-Van/CS50/tree/master/Web

分别实现了两个版本,一个在python+flask端进行排序,另一个在JavaScript端实现大部分代码逻辑。

目标效果如图所示。图片第一行显示成绩的排序方式,即按姓名、平时成绩、期中成绩、期末成绩、总成绩中的某一项升序或降序排列;单击提交按钮,则按指定的方式排序成绩;
在这里插入图片描述

说明

总成绩的计算方式是:平时成绩和期中成绩占30%,期末成绩占 40%,四舍五入。Python 提供round()函数,具体用法查找相关资料。

成绩表格采用HTML 表格实现。该表格的部分代码如下:

<table cellpadding='0' cellspacing='0' border='1'>
<tr><th>姓名</th><th>平时成绩</th><th>期中成绩</th><th>期末成绩
</th><th>总成绩</th><th>可视</th></tr>
<tr><td>陈子昂
</td><td>97</td><td>100</td><td>94</td><td>97</td><td><div
class='stateDiv' style='backgroundcolor:
Green;width:97px;'></div></td></tr>
</table>

“可视”列中颜色的宽度与分数相关,大于等于85 为绿色Green,大 于等于60 小于85 为黄色Yellow,其余为红色Red;

在head-style 中定义th、td 和stateDiv 的style 内容,使其效果尽量与图片一致,不要求绝对一致;

相关数据在附件中(poetScore.txt),不得用其他源数据;

附加要求:可分别实现两个版本,即排序在Python+Flask 端完成, 或者在浏览器端的网页部分完成。另外,思考如果有分数录入模块,分数查看模块,浏览器端排序是否会导致数据不一致,如何规避?

要点总览

要点1:web的基本结构

https://www.liaoxuefeng.com/wiki/1016959663602400/1017804650182592

现在主流的web架构都是客户端+服务器结构,粗略地讲,用户界面就是前端,实现我们客户端浏览器中看到的部分的工作;服务器逻辑叫后端,虽然并不是很容易理解这个工作,但其实服务器也没什么玄学的,如果服务器请求频率够低且逻辑够简单,你完全坐在一个盒子里,对外界的请求进行相对应的反馈,这时你就是一个服务器

http协议在二者的沟通中发挥非常重要的作用,详情了解https://blog.csdn.net/huangwwu11/article/details/23531111

我们这里只使用get和post,前者是从服务器端取,后者也可以传一些数据回服务器。

要点2:前端常用的语言

前端常用的语言三件套是HTML+CSS+JavaScript,简单地说,HTML构筑页面基本结构;CSS为网页提供样式的规定,负责美化;JavaScript增添一些交互的逻辑。

由于这个系列重点是熟悉Python,所以如果对web开发没有足够了解的话,可以选择性地学习以下内容
HTML语言30分钟入门:https://www.runoob.com/w3cnote/html-30-minutes-introductory-tutorial.html
HTML官方教程:https://www.w3school.com.cn/h.asp
HTML菜鸟教程:https://www.runoob.com/html/html-tutorial.html
JavaScript语言快速入门:https://www.w3cschool.cn/programming/programming-javascript.html

但实际上这个项目中使用到的HTML只有表格内容,只需按行进行复制粘贴。

对于表格整体,我们只需要构建

<table cellpadding='0' cellspacing='0' border='1'>
<tr><th>姓名</th><th>平时成绩</th><th>期中成绩</th><th>期末成绩
</th><th>总成绩</th><th>可视</th></tr>
<tr><td>陈子昂
</td><td>97</td><td>100</td><td>94</td><td>97</td><td><div
class='stateDiv' style='backgroundcolor:
Green;width:97px;'></div></td></tr>
</table>

另外JavaScript也非常类似于C++,上手比较简单。

在HTML中引入JavaScript脚本需使用script标签,我们以如下的一段js代码来讲解js的基本使用逻辑:

$("#btn1").click(function () { // 美元符号‘$’是jquery选择器,
							// 使用#进行选择是选出id为btn1的按键(.是按class进行选择,什么都不加就是按name选择)
							// click是点击
      var basis = $("[name='basis']").filter(":checked").attr("id"); //var是JavaScript中的变量,这里选择name为basis的一个html元素,找出哪个radio被按下,具体见后文有叙述
      var ascending = $("[name='order']").filter(":checked").attr("id"); //同上
      $.post("/sortby", { // post操作,这里sortby用于对接指定flask端的函数
          Basis: basis, // 指明flask端函数的变量,用js端的结果替代
          Ascending: ascending
      },
          function (data) {
              $("tr").remove("#rd"); //去除表格模板中上一次的内容
              $("table").append(data); //然后逐次添加
              if (ascending == '1'){  //简单的分支逻辑,用于表示标题
                  $('title').text('按'+basis+'升序');
                  $('h1').text('按'+basis+'升序');}
              else{
                  $('title').text('按'+basis+'降序');
                  $('h1').text('按'+basis+'降序');}
          }
      );
  });//绑定click事件到button元素,function()相当于Python的lambda函数

CSS的部分完全可以留在HTML中,我们出于练习,将其抽出来
在这里插入图片描述

要点3:Python的web开发框架:Flask

越往后学,就会发现示例代码越发重要,前人开发的工具可能很好用,但如果要具体搞懂每个细节,可能会影响到实用的效率。

比如Flask中常用的的装饰器,如果初次使用,难免会一头雾水。但对于初学或者只是偶然使用来完成一个任务,那么完全可以直接上手拼贴代码(尽管这从长远来看可能并不健康

详细内容可见官方文档https://dormousehole.readthedocs.io/en/latest/index.html
或者博客https://zhuanlan.zhihu.com/p/43929790

本文中使用的静态html都会给出,所以使用的Flask相关内容都会非常简单

import flask # 导入Flask模块
 
app = flask.Flask(__name__) # 初始化Flask对象
 
@app.route("/hello")
def hello():
    return flask.render_template("hello_world.html") # 直接使用模板html进行渲染
 
if __name__ == '__main__':
    app.run()

在静态模板html中,我们只需要用双大括号将待导入数据名称括起来即可,注意要加一个safe的过滤器,否则row这个变量中的字符串将不会行使html代码的作用。
在这里插入图片描述

具体实现

数据组织

在python文件中利用二维列表读入名称并计算,并排序。

本来尝试用json组织数据

def txt2json():
    with open("poetScore.txt", 'r', encoding='utf-8') as f:
        f.readline()
        data = ""
        for line in f.readlines():
            content = line.split(',')
            score = round((int(content[1])+int(content[2])+int(content[3]))/3)
            data = data + \
                f'{{"name":"{content[0]}","usual":{content[1]},"midterm":{content[2]},"final":{content[3]},"score":{score}}},'
        data = data[:-1]
    with open("poetScore.json", 'w', encoding='utf-8') as f:
        f.write(data)   

但是由于数据中的重复,所以我们不采用这个方法。显然作为一个排名,其中可能存在重名。

with open('poetScore.txt', 'r', encoding='utf-8') as f:
    df1 = pd.DataFrame(pd.read_csv('poetScore.txt'))

结合这两篇文章:

dataFrame添加列:https://blog.csdn.net/zx1245773445/article/details/99445332

行的遍历:https://blog.csdn.net/ls13552912394/article/details/79349809

我们利用列表生成式将rawdata进行部分分析:

df.insert(4, '总成绩', [round(int(row['平时成绩'])*0.3+int(row['期中成绩'])*0.3+int(row['期末成绩'])*0.4)
                         for _, row in df.iterrows()])

动态部分的构建

dataframe排序:https://blog.csdn.net/houyanhua1/article/details/87804111

df2 = read_data().sort_values(by=sb, ascending=asd) # dataframe排序是容易的

排序之后,利用Python生成HTML表格代码

def draw_table(df: pd.DataFrame, sortby:str) -> str:
    rowdata = ''
    for _, row in df.iterrows():
        rowunit = f"<tr id='rd'><td>{row['name']}</td><td>{row['平时成绩']}</td><td>{row['期中成绩']}</td><td>{row['期末成绩']}</td><td>{row['总成绩']}</td>"
        if sortby == "姓名":
            rowdata += rowunit
            rowdata += '</tr>'
        else:
            vis = row[sortby]
            color = choose_color(vis)
            rowunit += f"<td><div class='stateDiv' style='background:{color}; width:{vis}px;'></div></td></tr>"
            rowdata += rowunit
    return rowdata

网页radio获取

https://blog.csdn.net/newborn2012/article/details/17289345

在jQuery中很容易利用选择器和过滤器获得被选项:

var basis = $("[name='basis']").filter(":checked").attr("id");
var ascending = $("[name='order']").filter(":checked").attr("id");

一个小问题:大小写敏感

flask和html中js脚本的交互,注意,网址虽然是大小写不敏感的,但是在写接口的时候是敏感的。

$.post("/sortby", {
    Basis: basis,
    Ascending: ascending
    },
    function (data) {
        $("tr").remove("#rd");
        $("table").append(data);
        if (ascending == '1'){
            $('title').text('按'+basis+'升序');
            $('h1').text('按'+basis+'升序');}
        else{
            $('title').text('按'+basis+'降序');
            $('h1').text('按'+basis+'降序');}
    }
);

如果其中的域名写作sortBy将无法识别。

拼音排名

使用xpinyin模块

p = Pinyin()
df.insert(5, '姓名', [p.get_pinyin(row['name'], tone_marks='numbers') for _, row in df.iterrows()])

但是如果将拼音列标记为‘姓名’,那么在显示的时候,就会列出所有的拼音,所以这里要对dataframe原来的‘姓名’列改名:

p = Pinyin()
df.insert(5, '姓名', [p.get_pinyin(row['name'], tone_marks='numbers') for _, row in df.iterrows()])

补充内容

尝试将df传到前端进行处理。

将json对象传递到前端

采用了https://www.cnblogs.com/lazyboy1/p/5015111.html中的方法5,但是模板中的替代数据是字符串即可。

flask代码如下,这是非常简单的:

@app.route('/')
def root():
    return render_template('render2.html', data=read_data().to_json(orient='records', force_ascii=False))

接下来的重要问题出现在我们不太熟悉的JavaScript上

然后是排序,https://www.jianshu.com/p/92c3bf42a7b1

排序在点击按钮的时候扫描得出模式,然后进行排序。

var basis = $("[name='basis']").filter(":checked").attr("id");
var ascending = $("[name='order']").filter(":checked").attr("id");
data = mysort(stu_data, basis, ascending);
$("tr").remove("#rd");
console.log($("table"));
$.each(data, function (index, value) {
    rowdata = `<tr id='rd'><td>${value.name}</td><td>${value.平时成绩}</td><td>${value.期中成绩}</td><td>${value.期末成绩}</td><td>${value.总成绩}</td>`
    if (basis !== "姓名") {
        var vis = value[basis];
        var color = choose_color(vis);
        rowdata += `<td><div class='stateDiv' style='background:${color}; width:${vis}px;'></div></td></tr>`;
    }
    rowdata += '</tr>';
    $("table tbody").append(rowdata);

    if (ascending == '1') {
        $('title').text('按' + basis + '升序');
        $('h1').text('按' + basis + '升序');
    }
    else {
        $('title').text('按' + basis + '降序');
        $('h1').text('按' + basis + '降序');
    }
});

但是第一次我们得到了如下的表格:
在这里插入图片描述

预估是table的html代码发生了问题。观察如下:
在这里插入图片描述

查找资料,我们发现这是因为jquery会自动生成tbody,将加入的tr直接加入进去。这时候如果按照如下代码分次加入,则会使得空的tr被提取到tbody中。

$.each(data, function (index, value) {
    $("table").append("<tr id='rd'>");
    $("table").append(`<td>${value.name}</td>`);
    $("table").append(`<td>${value.平时成绩}</td>`);
    $("table").append(`<td>${value.期中成绩}</td>`);
    $("table").append(`<td>${value.期末成绩}</td>`);
    $("table").append(`<td>${value.总成绩}</td>`);
    if (basis !== "姓名") {
        var vis = value[basis];
        var color = choose_color(vis);
        $("table").append(`<td><div class='stateDiv' style='background:${color}; width:${vis}px;'></div></td></tr>`);
    }
    $("table").append('</tr>');
});

因而我们可以通过一次性添加的方式来解决这个问题:

$.each(data, function (index, value) {
    rowdata = `<tr id='rd'><td>${value.name}</td><td>${value.平时成绩}</td><td>${value.期中成绩}</td><td>${value.期末成绩}</td><td>${value.总成绩}</td>`
    if (basis !== "姓名") {
        var vis = value[basis];
        var color = choose_color(vis);
        rowdata += `<td><div class='stateDiv' style='background:${color}; width:${vis}px;'></div></td></tr>`;
    }
    rowdata += '</tr>';
    $("table").append(rowdata);
});

问题显然已经得到了解决:

在这里插入图片描述

当然我们也可以自己编写tbody来避免这个问题。这篇博客https://blog.csdn.net/wangdabin_1216/article/details/8206043给出的方法是写全tbody,减小不确定性。

function add(udata){$("table tbody").append(udata);}
add("<tr id='rd'>");
add(`<td>${value.name}</td>`);
add(`<td>${value.平时成绩}</td>`);
add(`<td>${value.期中成绩}</td>`);
add(`<td>${value.期末成绩}</td>`);
add(`<td>${value.总成绩}</td>`);
if (basis !== "姓名") {
    var vis = value[basis];
    var color = choose_color(vis);
    add(`<td><div class='stateDiv' style='background:${color}; width:${vis}px;'></div></td></tr>`);
}
add('</tr>');

为了避免更多次修改source代码,这种方法不一定是更好的,但不犯懒,去定义tbody一定是没错的。

完整代码

结构如下:
在这里插入图片描述

render.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>排名</title>
    <style>
        th {
            border: 1px solid;
            width: 100px;
        }

        td {
            width: 100px;
            height: auto;
            text-align: center;
            vertical-align: middle;
            border: 1px solid;
        }

        table {
            border-spacing: 0;
            border-collapse: collapse;
            border: 1px solid;
            margin: auto;
        }

        .stateDiv {
            height: 20px;
        }

        h1{
            text-align: center;
        }
    </style>
    
</head>

<body>
    <h1>默认按总成绩降序</h1>
    排序方式:
</br>
    <input type="radio" name="basis" id="姓名">姓名
    <input type="radio" name="basis" id="平时成绩">平时成绩
    <input type="radio" name="basis" id="期中成绩">期中成绩
    <input type="radio" name="basis" id="期末成绩">期末成绩
    <input type="radio" name="basis" id="总成绩">总成绩
</br>
    <input type="radio" name='order' id='1'>升序
    <input type="radio" name="order" id='0'>降序
</br>
    <button id="btn1">确定</button>
</br>        

    <table>
        <tr>
            <th>姓名</th>
            <th>平时成绩</th>
            <th>期中成绩</th>
            <th>期末成绩</th>
            <th>总成绩</th>
            <th>可视</th>
        </tr>
        {{row|safe}}
    </table>
    <script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
    <script>
        $("#btn1").click(function () {
            var basis = $("[name='basis']").filter(":checked").attr("id");
            var ascending = $("[name='order']").filter(":checked").attr("id");
            $.post("/sortby", {
                Basis: basis,
                Ascending: ascending
            },
                function (data) {
                    $("tr").remove("#rd");
                    $("table").append(data);
                    if (ascending == '1'){
                        $('title').text('按'+basis+'升序');
                        $('h1').text('按'+basis+'升序');}
                    else{
                        $('title').text('按'+basis+'降序');
                        $('h1').text('按'+basis+'降序');}
                }
            );
        });//绑定click事件到button元素,function()相当于Python的lambda函数
    </script>

</body>

</html>

render2.html

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>排名</title>
    <!-- css -->
    <style>
        th {
            border: 1px solid;
            width: 100px;
        }

        td {
            width: 100px;
            height: auto;
            text-align: center;
            vertical-align: middle;
            border: 1px solid;
        }

        table {
            border-spacing: 0;
            border-collapse: collapse;
            border: 1px solid;
            margin: auto;
        }

        .stateDiv {
            height: 20px;
        }

        h1 {
            text-align: center;
        }
    </style>

</head>

<body>
    <h1>默认按总成绩降序</h1>
    排序方式:
    </br>
    <input type="radio" name="basis" id="姓名">姓名
    <input type="radio" name="basis" id="平时成绩">平时成绩
    <input type="radio" name="basis" id="期中成绩">期中成绩
    <input type="radio" name="basis" id="期末成绩">期末成绩
    <input type="radio" name="basis" id="总成绩">总成绩
    </br>
    <input type="radio" name='order' id='1'>升序
    <input type="radio" name="order" id='0'>降序
    </br>
    <button id="btn1">确定</button>
    </br>

    <table>
        <thead>
            <tr>
                <th>姓名</th>
                <th>平时成绩</th>
                <th>期中成绩</th>
                <th>期末成绩</th>
                <th>总成绩</th>
                <th>可视</th>
            </tr>
        </thead>
        <tbody>

        </tbody>
    </table>
    
    <script src="https://cdn.staticfile.org/jquery/3.6.0/jquery.min.js"></script>
    <script>
        stu_data = eval('{{data|safe}}');
        $("#btn1").click(function () {
            var basis = $("[name='basis']").filter(":checked").attr("id");
            var ascending = $("[name='order']").filter(":checked").attr("id");
            data = mysort(stu_data, basis, ascending);
            $("tr").remove("#rd");
            console.log($("table"));
            $.each(data, function (index, value) {
                rowdata = `<tr id='rd'><td>${value.name}</td><td>${value.平时成绩}</td><td>${value.期中成绩}</td><td>${value.期末成绩}</td><td>${value.总成绩}</td>`
                if (basis !== "姓名") {
                    var vis = value[basis];
                    var color = choose_color(vis);
                    rowdata += `<td><div class='stateDiv' style='background:${color}; width:${vis}px;'></div></td></tr>`;
                }
                rowdata += '</tr>';
                $("table tbody").append(rowdata);

                if (ascending == '1') {
                    $('title').text('按' + basis + '升序');
                    $('h1').text('按' + basis + '升序');
                }
                else {
                    $('title').text('按' + basis + '降序');
                    $('h1').text('按' + basis + '降序');
                }
            });
        });

        function mysort(array, key, asd) {
            return array.sort(function (a, b) {
                var x = a[key]; var y = b[key];
                res = ((x < y) ? -1 : ((x > y) ? 1 : 0));
                if (asd == "1") { return res; }
                return -1 * res;
            });
        }
        function choose_color(vis) {
            return ((vis >= 85) ? "Green" : ((vis >= 60) ? "Yellow" : "Red"));
        };
    </script>

</body>

</html>

studScore.py

'''
@file: studScore.py
@author: 范皓年 1900012739 电子学系
@description: (基础版)
              js端获得radio选项
              python端利用dataframe进行排序
              生成html表格字符串
              传递到js端进行渲染
'''

from flask import Flask, request, render_template
import pandas as pd
from xpinyin import Pinyin

'''
# read_data->dataFrame:六列,分别为汉字姓名,平时成绩、期中成绩、期末成绩、总成绩,拼音姓名(便于按照音序进行排序)
'''
def read_data() -> pd.DataFrame:
    with open('poetScore.txt', 'r', encoding='utf-8') as f:
        df = pd.DataFrame(pd.read_csv('poetScore.txt'))
    df.insert(4, '总成绩', [round(int(row['平时成绩'])*0.3+int(row['期中成绩'])*0.3+int(row['期末成绩'])*0.4)
                         for _, row in df.iterrows()])
    df.rename(columns={'姓名': 'name'}, inplace=True)
    p = Pinyin()
    df.insert(5, '姓名', [p.get_pinyin(row['name'], tone_marks='numbers') for _, row in df.iterrows()])
    return df

'''
# choose_color->str: 
# 返回一个字符串,表示成绩对应的颜色
'''
def choose_color(grade):
    if grade >= 85:
        return 'Green'
    elif grade >= 60:
        return 'Yellow'
    else:
        return 'Red'


'''
# draw_table->str:
# 将dataframe数据转化成html表格格式的字符串
'''
def draw_table(df: pd.DataFrame, sortby:str) -> str:
    rowdata = ''
    for _, row in df.iterrows():
        rowunit = f"<tr id='rd'><td>{row['name']}</td><td>{row['平时成绩']}</td><td>{row['期中成绩']}</td><td>{row['期末成绩']}</td><td>{row['总成绩']}</td>"
        if sortby == "姓名":
            rowdata += rowunit
            rowdata += '</tr>'
        else:
            vis = row[sortby]
            color = choose_color(vis)
            rowunit += f"<td><div class='stateDiv' style='background:{color}; width:{vis}px;'></div></td></tr>"
            rowdata += rowunit
    return rowdata


app = Flask(__name__)


@app.route("/")
def root():
    df1 = read_data().sort_values(by="总成绩", ascending=False)
    rowdata = draw_table(df1,"总成绩")
    return render_template('render.html', row=rowdata)


@app.route("/sortby", methods=("post",))
def sortBy():
    sb = request.form["Basis"]
    asd = int(request.form["Ascending"])
    df2 = read_data().sort_values(by=sb, ascending=asd) # dataframe排序是容易的
    rowdata = draw_table(df2,sb)
    return rowdata


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, debug=True)

studScore_js.py


'''
@file: studScore.py
@author: 范皓年 1900012739 电子学系
@description: js端获得radio选项
              js获取python端以json格式传递的dataframe
              js端进行排序、颜色选择和html表格生成
'''
from flask import Flask, jsonify, render_template
import pandas as pd
from xpinyin import Pinyin


def read_data() -> pd.DataFrame:
    with open('poetScore.txt', 'r', encoding='utf-8') as f:
        df = pd.DataFrame(pd.read_csv('poetScore.txt'))
    df.insert(4, '总成绩', [round(int(row['平时成绩'])*0.3+int(row['期中成绩'])*0.3+int(row['期末成绩'])*0.4)
                         for _, row in df.iterrows()])
    df.rename(columns={'姓名': 'name'}, inplace=True)
    p = Pinyin()
    df.insert(5, '姓名', [p.get_pinyin(row['name'], tone_marks='numbers')
                        for _, row in df.iterrows()])
    return df


app = Flask(__name__)


@app.route('/')
def root():
    return render_template('render2.html', data=read_data().to_json(orient='records', force_ascii=False))


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, debug=True)

总结

一个思想:为了实用可以牺牲对细节的过度关心。使用框架的核心思想。
几个要点

  • 网页开发逻辑:客户端-服务器/前端、后端,http协议
  • 前端开发三件套:HTML,CSS,JavaScript
  • Python后端开发:Flask框架
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值