python 学习笔记(6)—— Flask 、MySql

目录

Flask

1、起步

2、渲染项目的首页

3、处理无参数的 GET 请求

4、处理有 query 参数的 GET 请求

6、处理 params 参数的 GET 请求 

6、处理 application/json 类型请求体的 POST 请求

7、根据参数渲染模板页面

 8、上传文件

9、路由处理函数的返回内容 和 配置内容安全策略

数据库操作(mysql)


Flask

1、起步

        先创建一个项目的文件夹,然后在该文件夹下初始化一个虚拟环境。虚拟环境建设好后,文件夹下将有一个 venv 的目录。

# python 3版本 创建虚拟环境:
py -3 -m venv venv

# 激活虚拟环境
venv\Scripts\activate

# 安装 flask
pip install flask

        依次创建 flaskr、flaskr/static、flaskr/templates、tests目录:

        flaskr目录:包含应用代码和文件的 Python 包;

                /static:存放静态文件,如图片等;

                /templates:可以用来存放 html 文件,用于服务端渲染;

        test 目录:用来存放测试类型的文件;

2、渲染项目的首页

from flask import Flask
from flask import render_template
from flask_cors import CORS            # pip install flask_cors

# 创建该类的实例
app = Flask(__name__)
CORS(app, resources={r'/*': {'origins' :'*'}})      # 同源策略解决方法


# 使用app.route()进行装饰的函数称为路由处理函数
# 使用装饰器设定匹配的URL,如果命中,则执行下面的函数;默认是 get 请求
@app.route('/')
def default_page():
    # 返回指定的页面,是项目下 templates 下的文件
    return render_template('index.html')

if __name__ == '__main__':
    # 运行该服务,监听的端口为 9090,host=‘0.0.0.0’表示接受公开访问
    app.run(port=9090, host='0.0.0.0')
# templates/index.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        h1{ color: brown; }
    </style>
</head>
<body>
    <h1>Hello, welcome to the first page.</h1>
</body>
</html>

3、处理无参数的 GET 请求

         改造 index.html 文件,让请求和结果展示在当前界面完成:

# templates/index.js

<body>
    <h1>Hello, welcome to the first page.</h1>
    <ul>
        <li>
            <button onclick="get_request('get_datetime', '.datetime')">click to get datetime</button>
            <p class="datetime"></p>
        </li>
    </ul>
<script>
    function get_request(path, renderElement, query='', callback){
        const xhr = new XMLHttpRequest()

        xhr.open('GET', `http://192.168.145.135:9090/${path}${query ?  `?${query}` : ''}`)

        xhr.onreadystatechange = () => {
            if(xhr.readyState === 4 && xhr.status){
                document.querySelector(renderElement).innerText = xhr.response
                callback ? callback(xhr.response) : ''
            }
        }

        xhr.send()
    }
</script>
</body>
# 规定匹配的路径为 /get_time 或 /get_time?
@app.route('/get_datetime')
def get_datetime():
    return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

4、处理有 query 参数的 GET 请求

        /templates/index.html 中新增的内容: 

        <li>
            <input class="name" type="text" placeholder="please input you name">
            <select class="gender" name="" id="">
                <option value="man">man</option>
                <option value="woman">woman</option>
            </select>
            <button onclick="request_args()">click to get datetime</button>
            <p class="name-gender"></p>
        </li>

<script>
    function request_args(){
        let name = document.querySelector('.name').value
        let gender = document.querySelector('.gender').value
        get_request('get_with_args', '.name-gender', `name=${name}&gender=${gender}`)
    }
</script>
from flask import Flask,request

@app.route('/get_with_args')
def get_with_args():
    args = request.args

    # 各参数可以从 request 实例上去获取
    # print(args['name'], args['gender'])

    name = args['name']
    gender = args['gender']
    return f'你好,帅气的{name}先生' if gender == 'man' else f'你好,美丽的{name}小姐'

        关于 request 更多内容可以参考: https://flask.net.cn/api.html#incoming-request-data

6、处理 params 参数的 GET 请求 

         html 文件新增的测试内容:

        <li>
            <input class="nickname" type="text" placeholder="input nickname">
            <input class="age" type="number" placeholder="input age">
            <button onclick="sendParams()">click to send params</button>
            <p class="nickname-and-age"></p>
        </li>

<script>
    const sendParams = () => {
        let nickname = document.querySelector('.nickname').value
        let age = document.querySelector('.age').value
        get_request(`test-params/${nickname}/${age}`, '.nickname-and-age', '', (resdata)=>{
            let strong = document.createElement('strong')
            let obj = JSON.parse(resdata)
            strong.innerText = `response data: nickname: ${obj.nickname}; age: ${obj.age}`
            document.querySelector('.nickname-and-age').append(strong)
        })
    }
</script>    
# 两个 params 参数都必须传, 预定义 nickname 参数为字符串,age 为整型
@app.route('/test-params/<string:nickname>/<int:age>')
def test_params(nickname, age):
    return {'nickname':nickname,'age':age}

6、处理 application/json 类型请求体的 POST 请求

        index.html 文件中新增的内容: 

        <li>
            <input class="username" type="text">
            <input class="passwd" type="password">
            <button class="post-btn">click to post</button>
            <p class="post-res"></p>
        </li>

<script>
    document.querySelector('.post-btn').addEventListener('click',()=>{
        fetch('http://localhost:9090/test-post',{
            method:'post',
            body:JSON.stringify({
                username:document.querySelector('.username').value,
                passwd:document.querySelector('.passwd').value
            }),
            headers:{
                'Content-Type':'application/json',
                'auth':'abcdefg---6789'
            }
        }).then(res => res.text()).then(data=>{
            document.querySelector('.post-res').innerText = data
        })
    })
</script>
@app.route('/test-post', methods=['post'])
def test_post():
    str = (request.data).decode()
    print(str)
    obj = json.loads(str)
    print(obj['username'])

    print(request.json['username'])
    
    # print(request.get_data())           # 与 request.data 一致,是字符串类型
    # print(request.json)                 # 是dict类型
    return 'ok'

7、根据参数渲染模板页面

        index.js 中添加的内容: 

        <li>
            <input type="text" placeholder="your address" class="_address">
            <input type="text" placeholder="your name" class="_name">
            <button class="to-new-page">click to new page with data</button>
        </li>

<script>
    document.querySelector('.to-new-page').addEventListener('click', ()=>{
        address = document.querySelector('._address').value
        name = document.querySelector('._name').value

        # 在新的窗口中访问
        window.open(`http://localhost:9090/new-page?address=${address}&name=${name}`)
    })
</script>

        新创建两个 html: 

# new-page.html   当有 name 时返回的页面,可以没有address

<body>
       {% if address %}
           <h1>Hi {
   
   {name}}, we know your address is {
   
   {address}}.</h1>
       {% else %}
           <h1>Hello {
   
   {name}}!</h1>
       {% endif %}
       <img src="https://tse3-mm.cn.bing.net/th/id/OIP-C.bVb769JBdzVZYuksxZ2Y-AHaEo?pid=ImgDet&rs=1" alt="">
</body>
# 404.html

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404</title>
    <style>
        body,html{
            margin: 0;
            padding: 0;
        }
        .outer{
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .num{
            width: 100px;
            height: 100px;
            font-size: 5rem;
            font-weight: 900;
            text-shadow: 3px 3px 3px #aaa;
        }
        .num:nth-child(2n){ color: brown; }
        .num:nth-child(2n + 1){ color: chocolate; }
        .num:nth-child(4){ width: auto; }
    </style>
</head>
<body>
    <div class="outer">
        <div class="num">4</div>
        <div class="num">0</div>
        <div class="num">4</div>
        <div class="num">no found</div>
    </div>
</body>
# 可以直接访问
@app.route('/new-page')
def new_page():
    # 设置 address 和 name 都不是必传参数
    address = request.args['address'] if request.args else ''
    name = request.args['name'] if request.args else ''
    if name:                    # 往模板页面传递两个参数
        return render_template('another.html', address=address, name=name)
    else:
        # 如果没有名字,则返回 404 的页面
        return render_template('404.html')

只有 name 时
只有 name 时

name 和 address 都没有时

name 和 address 都具备时

 8、上传文件

        index.html 中新增的内容: 

        <li>
            <input type="file" accept=".jpg,.png,.gif" id="file">
            <button class="upload-file" onclick="uploadFile()">upload file</button>
            <p class="upload-file-res"></p>
        </li>

<script>
    function uploadFile(){
        let files = document.querySelector('#file').files

        const formdata = new FormData()
        formdata.append('file', files[0])
        formdata.append('time', new Date().getTime())

        fetch('http://localhost:9090/upload-file',{
            method:'post',
            body:formdata
        }).then(res=>res.text()).then(data=>{
            console.log(data)
        })
    }
</script>
@app.route('/upload-file', methods=['post'])
def upload_file():
    if 'file' not in request.files:
        return 'upload fail'

    file = request.files['file']

    if file.filename == '':
        return 'filename no found'

    file.save(f'./{file.filename}')        # 保存图片到本地

    return 'ok'

关于 flask 文件上传的更安全的处理:上传文件_Flask中文网

9、路由处理函数的返回内容 和 配置内容安全策略

        每一个使用 app.route() 进行装饰的路由处理函数都需要有返回值,是服务端返回给客户端的响应体内容。该返回值可以是一个字符串,也可以是一个元组(包含状态码、响应体等的信息)等的形式。

        可以使用 make_response() 来构造响应内容。

from flask import request, make_response


@app.route('/')
def website():
    ip = request.remote_addr          # 获取用户的ip
    agent = request.user_agent        # 获取用户访问时的设备信息(浏览器信息)
    print('ip:',ip)
    print('agent:',agent)

    # 设置内容安全策略 report-uri /CSP-feedback 将违反安全策略的情况使用post请求发送到当前项目的 /CSP-feedback 路由处理函数中进行处理
    csp_header = {
        'Content-Security-Policy': "default-src 'self' 'unsafe-inline';script-src 'self';report-uri /CSP-feedback",
        'X-Content-Type-Options': 'nosniff',
        'X-Frame-Options': 'DENY',
        'X-XSS-Protection': '1; mode=block'
    }
    temp = render_template('index.html')

    res = make_response(temp, 200)        # 设置响应的html内容和状态码
    res.headers.extend(csp_header)        # 将内容安全策略添加到响应头

    return res


@app.route('/CSP-feedback',methods=['post'])
def CSP_feedback():
    data = request.data.decode()            # 获取响应体的内容
    ip = request.remote_addr
    print(data, ip)
    return {}


@app.route('/test')  
def hello_world():  
    response = make_response('Hello, World!')      # 设置响应内容
    response.status_code = 200              # 单独设置状态码
    response.headers['Content-Type'] = 'text/plain'      # 设置响应体类型
    return response


        访问时 控制台显示出违反安全策略的详情内容: 

{
    "csp-report":{
        "blocked-uri":"inline",
        "column-number":78,
        "disposition":"enforce",
        "document-uri":"http://127.0.0.1:9090/",
        "effective-directive":"script-src-elem",
        "line-number":249,
        "original-policy":"default-src 'self' 'unsafe-inline'; script-src 'self'; report-uri http://127.0.0.1:9090/CSP-feedback",
        "referrer":"",
        "source-file":"moz-extension",
        "status-code":200,
        "violated-directive":"script-src-elem"
    }
}

"blocked-uri":被CSP阻止的资源URI。"inline"意味着CSP阻止了在HTML文档中直接插入内联脚本(即<script>标签)。

"column-number"、"line-number":在源代码中违规的列号和行号。

"disposition":表示处理的方式。enforce 表示强制执行CSP策略,阻止该违规行为的执行。

"effective-directive"、"violated-directive":正在被触发的CSP指令 和 违反的CSP指令。script-src-elem 要求 script 的来源必须符合要求

数据库操作(mysql)

        安装 pymysql:

pip install pymysql
import pymysql

# 连接指定数据库
conn = pymysql.connect(host='localhost', user='root',
                       password='abcdef',database='python_study',
                       charset='utf8')

# 定义一个游标对象,靠游标对象来执行sql 以及获取 结果
#cursor = conn.cursor()          # 默认是元组形式的结果

# 将游标对象定义为 字典 类型,进而更方便地获取数据
from pymysql.cursors import DictCursor
cursor = conn.cursor(DictCursor)            # 创建游标,定义返回的数据类型是字典

        查询语句的执行和结果获取:

# 创建并执行 执行sql语句
sql = 'select * from student'
cursor.execute(sql)

# 获取执行的结果集 所有数据
res = cursor.fetchall()
print(res)

# res = cursor.fetchmany(3)                   # 获取前三条结果

        关于更新类的操作(增、删、改):

# 更新一条数据
sql = "update student set age = 9 where num = '1000'"
cursor.execute(sql)
conn.commit()                       # 显式提交更新操作



# 新增一条数据,注意格式化的sql语句,如果有引号的要加上引号
name = '许九'
age = 10
gender = '男'
num = '1006'
sql = f"insert into student values ('{name}','{num}','{gender}',{age});"
cursor.execute(sql)
conn.commit()

        最后需要关闭连接:

# 关闭连接
conn.close()

        如果 连接的时候报错:RuntimeError: 'cryptography' package is required for sha256_password or caching_sha2_password auth methods

        该错误指出需要 'cryptography' 包来处理 'sha256_password' 或 'caching_sha2_password' 的认证方式。

        这可能是因为 Python 环境中缺少了 'cryptography' 包。这个包是一个 Python 的加密库,它提供了许多加密算法的实现,包括 'sha256_password' 和 'caching_sha2_password' 这两种 MySQL 的密码加密方式。

        直接安装这个包然后再运行程序即可:

pip install cryptography

        

 参考:

[1] flask获取post请求参数_flask post参数_阿常呓语的博客-CSDN博客

[2] 快速上手_Flask中文网 

猜你喜欢

转载自blog.csdn.net/hao_13/article/details/132829474