2024-07-10
python开发
00
请注意,本文编写于 90 天前,最后修改于 90 天前,其中某些信息可能已经过时。

目录

python笔记
Flask
路由配置篇
基本使用
综合案例——音乐网站
返回数据(html,json,重定向)
路由配置高级篇
蓝图
路由传参
Flask获取前端传入的数据
包含在url中的数据
json格式
文件格式
Flask的jinjia2模板语言
模板中的数据展示
模板的循环以及条件语句
模板的继承
在模板中引用静态文件
模板中的 | 语法(过滤器)
测试器
数据库篇
数据库配置以及模型建立
提交表结构
使用ORM模型进行增删改查
表单验证
定义验证表格
使用验证表格
配置全局变量
配置登录信息
Django
数据库篇
连接数据库以及配置模型

python笔记

Flask

路由配置篇

基本使用

综合案例——音乐网站

https://github.com/xishandong/Music_Web


在我们创建好的项目中,我们有一个基础的路由,如下所示:

python
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): # put application's code here ... if __name__ == '__main__': app.run()

其中的@app.route('/')就是我们需要的路由,我们运行这个就可以打开对应的网页。

返回数据(html,json,重定向)

  • 返回html数据

    • 纯粹的网页数据

    我们可以使用模板进行返回数据,我们需要从flask中导入render_template,然后将我们需要的网页模板放在templates目录中即可。

    python
    from flask import Flask, render_template app = Flask(__name__) @app.route('/') def hello_world(): # put application's code here return render_template('index.html') if __name__ == '__main__': app.run()

    这样我们就可以在对应的页面看见index.html中的内容了。

    • 后端给前端传数据

      我们只需要在返回数据的时候传入对应的值即可。

    python
    @app.route('/') def hello_world(): # put application's code here something = [], {}, '', 1 return render_template('index.html', something=something)

    ​ 我们在前端用{{ something }}即可接受到传递的数据

  • 返回json数据

    我们需要从flask导入jsonify,像这样就可以传递json信息了。

python
from flask import jsonify @app.route('user/', methods=['POST']) def user_json(): return jsonify({'success': 200, 'message': '请登陆后进行操作'})
  • 重定向

    使用这个功能我们需要从flask导入redirect

python
from flask import redirect, url_for @app.route('/refer', method=['GET', 'POST']) def refer(): return redirect('/') # 表示重定向到/路由下 # 这个是重定向到蓝图中的user的login函数 # return redirect(url_for("user.login"))

路由配置高级篇

蓝图


我们在实际开发时,由于代码量会很大,如果我们把所有内容都放到app.py一个文件中去编写,那么无疑是很痛苦的一件事情,这个时候就可以用到Flask的蓝图函数。

我们需要在项目中创建一个文件夹,可以叫blueprints,我们在里面就可以编写蓝图了。

  • 在app中
python
from blueprints.user import bp as user_bp # 注册蓝图 app.register_blueprint(user_bp)
  • 在蓝图文件中
python
from flask import Blueprint bp = Blueprint("user", __name__, url_prefix='/')

然后就可以正常写路由了。

路由传参


我们可以在url中自定义参数进行获取,如下所示

python
# 可以定义类型 @app.route('/blog/<int: blog_id>') @app.route('/blog/<blog_id>') def blog_detail(blog_id): return render_template("blog_detail.html", blog_id=blog_id)

Flask获取前端传入的数据

包含在url中的数据


python
@app.route('/book/list') def book_list(): # argument: 参数 # request.args: 字典类型 page = request.args.get("page", default=1, type=int) return f"你获取的是{page}"

json格式


  • 前端指定了contentType: 'application/json;charset=UTF-8'
python
@bp.route('user/search_add', methods=['POST']) def search_add(): query = request.json.get('query') offset = request.json.get('offset') return "成功"
  • 前端没有指定json,而是表单传输
python
@bp.route('user/addPlaylist', methods=['POST']) def add_playlist(): name = request.form.get('playlistName') desc = request.form.get('playlistDesc') return "成功"

文件格式


python
@bp.route('user/upload_cover/<id>', methods=['POST']) def upload_cover(id): file = request.files['file'] filename = file.filename file.save(save_path) return jsonify({'success': 200, 'file': save_path})

在js中:

javascript
var avatar = $("#avatar") avatar.click(function () { $("#fileInput").click(); }); $("#fileInput").change(function () { var fileInput = document.getElementById("fileInput"); var file = fileInput.files[0]; var formData = new FormData(); formData.append("file", file); $.ajax({ url: "/upload", type: "POST", data: formData, processData: false, contentType: false, success: function (response) { if (response.file) { $("#avatar").attr("src", response.file); } }, error: function (error) { console.log(error); } }); });

在html中:

html
<!-- 上传头像 --> <input type="file" id="fileInput" style="display: none;">

Flask的jinjia2模板语言

模板中的数据展示


在flask中,开发者为我们提供了一种jinjia2的模板语言,可以配合flask后端使用。

jinja2
<div> {{ message }} </div>

这样就可以展示后端传递给html的信息了,如何传递信息见上文Flask-路由配置篇-返回数据下。

模板的循环以及条件语句


  • 循环语句

    在jinjia2中,我们用下述方式进行循环,注意循环会完全遍历,不能中途break掉。假设后端给模板传递了一个items的可迭代数据:

jinja2
{% for item in items %} <div> {{ item }} </div> {% endfor %}
  • 条件语句

    类似于上述写法,jinjia2也可以进行条件语句的编写

jinja2
{% if something %} <div> 条件语句为真 </div> {% else %} <div> 条件语句为假 </div> {% endif %}

模板的继承


如果我们的网页有很多重复的内容,例如导航栏以及页脚等内容,我们可以用模板继承的写法来编写,然后再父模板非公共部分划分出block即可让子模版在非公共地方编写。

  • 在父模板中

    假设父模板的名字叫base.html

jinja2
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> <link> {% block head %}{% endblock %} </head> <body> {% block body %}{% endblock %} </body> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> {% block tail %}{% endblock %} </html>
  • 在子模版中
jinja2
{%extends "base.html" %} {% block head %} <link> {% endblock %} {% block body %} <div> 我是子模版 </div> {% endblock %} {% block tail %} <script></script> {% endblock %}

在模板中引用静态文件


  • 引用网络静态文件

    这个和普通的html文件一样。

jinja2
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.0.3/css/font-awesome.css"> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  • 引用本地静态文件

    我们在模板中使用本地的静态文件,可以使用url_for来实现,例如:

jinja2
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> <script src="{{ url_for('static', filename='js/base.js') }}"></script> <img id="avatar" src="{{ url_for('static', filename='img/profile.webp') }}"
  • 页面跳转

    我们在一个页面想要跳转到其他页面时,也需要使用url_for,下面将展示两种不同的情况:

jinja2
<!-- 这个hello_world时app里面的函数 --> <a href="{{ url_for('hello_world') }}" class="item-link" id="pageLink"> <!-- playlists是user蓝图中的函数 --> <a href="{{ url_for('user.playlists') }}" class="item-link icon-fill" id="pageLink">

模板中的 | 语法(过滤器)


总所周知,模板我们可以使用{{ something }}来展示后端给前端的数据,但是我们如何对这个数据进行一些简单的处理呢?这就要用到模板语言中的 | 语法。

首先,我们来看一个相对复杂的案例:

html
<li>推荐指数<span>{{ (10 - ((score - song.score | float) | abs | round(2) )) | round(2) }}</span></li>

这个是什么意思呢?首先第一个是将分数转化为浮点数,然后用另外一个分数去做差,然后去绝对值,同时保留两位小数,然后用10去做差,再保留两位小数。当然,jinjia2还支持更多的运算,下面将列出常见的用法:

过滤器名称说明例子
safe渲染时值不转义

{{ 'name' |safe }}

escapeHTML转义,即使autoescape关了也转义,可以缩写为e{% autoescape false %}

{{ 'name' |escape }}

{% endautoescape %}
capitialize把值的首字母转换成大写,其他子母转换为小写

{{ 'hello world' |capitalize }}

lower把值转换成小写形式

{{ 'XML' |lower }}

upper把值转换成大写形式

{{ 'hello world' |upper}}

title把值中每个单词的首字母都转换成大写

{{ 'hello world' |title}}

trim把值的首尾空格去掉

{{ ' hello ' |trim }}

striptags渲染之前把值中所有的HTML标签都删掉
join拼接多个值为字符串
replace替换字符串的值
round默认对数字进行四舍五入,也可以用参数进行控制

{{ 12.8888 |round(2, 'floor') }}

int把值转换成整型

{{ 12.8888 |int }}

float把值转化为小数{{ song.score | float }}
abs把值转化为正数

{{ -12 |abs }}

reverse字符串反转

{{ 'hello' |reverse }}

default增加默认值

{{ name |default('No name') }}

format格式化显示

{{ '%s is %d' |format("Number", 99) }}

first取第一个元素

{{ [1,2,3] |first }}

last取最后一个元素

{{ [1,2,3] |last }}

length返回列表长度,可以写为count

{{ [1,2,3,4,5] |length }}

sum列表求和

{{ [1,2,3,4,5] |sum }}

sort(列表)列表排序,默认为升序

{{ [3,2,1,5,4] |sort }}

join合并为字符串

{{ [1,2,3,4,5] |join(' |') }}

sort(字典)按指定字段排序,这里设reverse为true使其按降序排
    {% for user in users |sort(attribute='age', reverse=true) %}
  • {{ user.name }}, {{ user.age }}
  • {% endfor %}
groupby列表分组,每组是一个子列表,组名就是分组项的值
    {% for group in users|groupby('gender') %}
  • {{ group.grouper }}
      {% for user in group.list %}
    • {{ user.name }}
    • {% endfor %}
  • {% endfor %}
map取字典中的某一项组成列表,再将其连接起来

{{ users |map(attribute='name') |join(', ') }}

见到了常见的过滤器之后,我们还可以在python内部进行自定义过滤器

python
# 第一种方式 def get_even_list(l): return l[::2] # 函数的第一个参数是过滤器函数,第二个参数是过滤器名称 app.jinja_env.filters['even_filter'] =get_even_list # 第二种方式 @app.template_filter() # 过滤器函数 def is_even(num): if num % 2 == 0: return "even number" else: return "odd number"
jinja2
<p>{{ [1,2,3,4,5] | even_filter }}</p> <p>{{ 2 | is_even }}</p>

测试器


测试器总是返回一个布尔值,它可以用来测试一个变量或者表达式,使用”is”关键字来进行测试。

jinja2
{% set name='ab' %} {% if name is lower %} <h2>"{{ name }}" are all lower case.</h2> {% endif %}

测试器本质上也是一个函数,它的第一个参数就是待测试的变量,在模板中使用时可以省略去。如果它有第二个参数,模板中就必须传进去。测试器函数返回的必须是一个布尔值,这样才可以用来给if语句作判断。

  • jinjia2内置测试器
jinja2
{# 检查变量是否被定义,也可以用undefined检查是否未被定义 #} {% if name is defined %} <p>Name is: {{ name }}</p> {% endif %} {# 检查是否所有字符都是大写 #} {% if name is upper %} <h2>"{{ name }}" are all upper case.</h2> {% endif %} {# 检查变量是否为空 #} {% if name is none %} <h2>Variable is none.</h2> {% endif %} {# 检查变量是否为字符串,也可以用number检查是否为数值 #} {% if name is string %} <h2>{{ name }} is a string.</h2> {% endif %} {# 检查数值是否是偶数,也可以用odd检查是否为奇数 #} {% if 2 is even %} <h2>Variable is an even number.</h2> {% endif %} {# 检查变量是否可被迭代循环,也可以用sequence检查是否是序列 #} {% if [1,2,3] is iterable %} <h2>Variable is iterable.</h2> {% endif %} {# 检查变量是否是字典 #} {% if {'name':'test'} is mapping %} <h2>Variable is dict.</h2> {% endif %}
  • 自定义测试器
python
# 自定义测试器 # 第一种方式 import re def test_tel(tel_num): tel_re = r'\d{11}' return re.match(tel_re,tel_num) app.jinja_env.tests['is_tel'] = test_tel # 第二种方式 @app.template_test('start_with') def start_with(str, suffix): return str.lower().startswith(suffix.lower())
jinja2
{% set tel = '18910171111' %} {% if tel is is_tel %} <h2>{{ tel }} is mobile phone</h2> {% endif %} {% set name = 'Hello world' %} {% if name is start_with 'hello' %} <h2>"{{ name }}" start_with "hello"</h2> {% endif %}

数据库篇

数据库配置以及模型建立


  • 首先安装好下述依赖

    bash
    pip install pymysql pip install sqlalchemy pip install flsk-sqlalchemy # 这个是进行提交的 pip install flask-migrate
  • 然后编写config.py文件

    python
    # MySQL的主机名 HOSTNAME = "127.0.0.1" # MySQL监听的端口, 默认3306 PORT = 3306 # 连接MySQL的用户名 USERNAME = "root" # 连接MySQL的密码 PASSWORD = "" # MySQL上连接的数据库名称 DATABASE = "test" # 配置 app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOSTNAME}:{PORT}/{DATABASE}?charset=utf8'
  • 编写exts.py文件

    python
    from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()

    这个文件的作用是解决了文件引用死锁

  • 在app.py中注册

    python
    from flask_migrate import Migrate from exts import db import config # 添加配置文件 app.config.from_object(config) db.init_app(app) migrate = Migrate(app, db)
  • 在models.py中编写数据库的表

    python
    from exts import db class User(db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(100), nullable=False) password = db.Column(db.String(500), nullable=False) email = db.Column(db.String(100), nullable=False, unique=True) interval = db.Column(db.Float(precision=1), nullable=False, default=2) avatar = db.Column(db.String(500), nullable=False, default='') songs = db.relationship('Song', secondary='user_song', backref=db.backref('users', lazy='dynamic')) playlists = db.relationship('Playlist', backref='users', cascade="all, delete-orphan") class UserSong(db.Model): __tablename__ = 'user_song' user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), primary_key=True) song_id = db.Column(db.String(100), db.ForeignKey('song.id', ondelete='CASCADE'), primary_key=True) class Song(db.Model): __tablename__ = "song" id = db.Column(db.String(100), primary_key=True) name = db.Column(db.String(100), nullable=False) singer = db.Column(db.String(100), nullable=False) cover_url = db.Column(db.String(500), nullable=False) score = db.Column(db.String(100), nullable=False) class Playlist(db.Model): __tablename__ = 'playlists' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(100), nullable=False) description = db.Column(db.String(200), nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) cover = db.Column(db.String(500), nullable=False, default='img/default_cover.png') songs = db.relationship('Song', secondary='playlist_song', backref=db.backref('playlists', lazy='dynamic', cascade="save-update, merge")) class PlaylistSong(db.Model): __tablename__ = 'playlist_song' playlist_id = db.Column(db.Integer, db.ForeignKey('playlists.id', ondelete='CASCADE'), primary_key=True) song_id = db.Column(db.String(100), db.ForeignKey('song.id', ondelete='CASCADE'), primary_key=True)

    上述表结构展现了常见的一对多以及多对多和外键约束

提交表结构

  • 在terminal输入以下命令

    bash
    flask db init flask db migrate flask db upgrade
  • 创建完成

使用ORM模型进行增删改查

使用这些模型就像是使用对象一样简洁

python
from models import User... @app.route('/user/add') def add_user(): user = User(username='张三', password='123456') db.session.add(user) db.session.commit() return "用户创建成功!" @app.route('/user/query') def query_user(): # 1. get: 根据主键 # user = User.query.get(1) # print(user.id, user.username, user.password) # 2.filter, 返回的是Query, list users = User.query.filter_by(username='张三') for user in users: print(user) return "数据查找成功" @app.route('/user/update') def update_user(): user = User.query.filter_by(username='张三').first() user.password = '2222' db.session.commit() return "数据修改成功" @app.route('/user/delete') def delete(): user = User.query.filter_by(username='张三').first() db.session.remove(user) db.session.commit() return "用户删除成功" @bp.route('user/deletePlaylistSong', methods=['POST']) def delete_PlaylistSong(): pid = request.form.get('pid') mid = request.form.get('mid') playlist = Playlist.query.filter_by(id=pid).first() song = Song.query.filter_by(id=mid).first() if playlist.user_id == g.user.id: if song in playlist.songs: playlist.songs.remove(song) try: db.session.commit() return jsonify({'success': 200, 'message': '歌曲从歌单中移除'}) except Exception as e: current_app.logger.error('user/deletePlaylistSong: %s', str(e)) return jsonify({'success': 200, 'message': '数据库错误'}) else: return jsonify({'success': 500, 'message': '错误操作!'})

表单验证

定义验证表格


在我们将用户提交的信息存入数据库之前一定要进行表单验证,这里我们使用wtforms来进行表单验证

bash
pip install wtforms

注: 我们在使用Email进行表单校验的时候还需要安装一个邮箱验证的第三方库,到时候根据报错信息自行安装即可。

  • 常规验证
python
import wtforms from wtforms.validators import Email, Length, EqualTo class RegisterForm(wtforms.Form): email = wtforms.StringField(validators=[Email(message="邮箱格式错误"), EmailVerify()]) username = wtforms.StringField(validators=[Length(min=3, max=20, message="用户名格式错误, 最少3位,至多20位!")]) password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误! 最少6位, 最多20位!")]) password_confirm = wtforms.StringField( validators=[EqualTo("password", message="两次输入密码不一致! 请检查密码输入!")]) class LoginForm(wtforms.Form): email = wtforms.StringField(validators=[Email(message="邮箱格式错误")]) password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误! 最少6位, 最多20位!")])

在上述代码中,我们对注册以及登录接口的数据进行了验证,但是在email我们加入了自定义验证,自定义验证见下文。

  • 自定义验证
python
class EmailVerify(wtforms.Form): def __call__(self, form, field): email = field.data user = User.query.filter_by(email=email).first() if user: raise wtforms.ValidationError(message="该邮箱已经被注册!")

我们定义自定义验证时,利用python的魔术方法__call__来定义类在调用时的方法,这里我们进行了数据库筛查,判断邮箱是否被注册,如果被注册就返回一个验证错误的错误信息。

下面我们来看一个另一种自定义的一个验证:

python
class RegisterForm(wtforms.Form): email = wtforms.StringField(validators=[Email(message="邮箱格式错误"), EmailVerify()]) captcha = wtforms.StringField(validators=[Length(min=4, max=4, message="验证码格式错误!")]) username = wtforms.StringField(validators=[Length(min=3, max=20, message="用户名格式错误, 最少3位,至多20位!")]) password = wtforms.StringField(validators=[Length(min=6, max=20, message="密码格式错误! 最少6位, 最多20位!")]) password_confirm = wtforms.StringField( validators=[EqualTo("password", message="两次输入密码不一致! 请检查密码输入!")]) def validate_captcha(self, field): captcha = field.data email = self.email.data confirm = EmailCaptcha.query.filter_by(email=email, captcha=captcha).first() if not confirm: raise wtforms.ValidationError(message="邮箱或验证码错误!")

我们在想要验证的字段前加入__validate___这个字段,就可以自定义验证这个字段。

使用验证表格


我们需要先导入我们自定义的验证表单

python
from .forms import RegisterForm, LoginForm

之后我们只需要将前端传入的表单数据直接传递给这个Form就可以了

python
form = LoginForm(request.form) if form.validate(): ... else: # return '表单验证失败' return redirect(url_for("user.login"))

配置全局变量

Flask中我们可以为整个后端配置一个全局变量,这个需要引入g,然后配置属性即可

python
from flask import g setattr(g, "user", user)

配置登录信息

我们想要给用户配置登录信息,需要用到一个session函数

在user的蓝图中,我们在登录的路由登录成功后添加一个session信息

python
import ... @bp.route('user/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template("login.html") elif request.method == 'POST': form = LoginForm(request.form) if form.validate(): email = form.email.data password = form.password.data user = User.query.filter_by(email=email).first() if not user: return redirect(url_for("user.login")) if check_password_hash(user.password, password): # cookie session['user_id'] = user.id return redirect("/") else: return redirect(url_for("user.login")) else: return redirect(url_for("user.login"))

之后在app中配置上下文以及请求

python
import ... # before_request/ before_first_request/ after_request @app.before_request def get_session(): # 在登录成功后设置cookie过期时间为一年 session.permanent = True app.permanent_session_lifetime = timedelta(days=365) user_id = session.get("user_id") if user_id: user = User.query.get(user_id) setattr(g, "user", user) else: setattr(g, "user", None) @app.context_processor def my_context_processor(): return {"user": g.user}

第一个是在每次发送请求的时候都在cookie中获取用户信息,如果有用户信息,就把他设置为全局变量的一个属性,更多的用法还有注释中的两种。

第二个是在上下文管理器中定义了user这个字段,这个字段就是可以在前端模板中,以{{ user }}访问到用户的信息。

注:在登录成功后设置cookie过期时间为一年,若不设置,默认浏览器关闭即清除登录状态。

Django

数据库篇

连接数据库以及配置模型


  • 首先在settings中配置好数据库的相关信息, 注:需要安装好mysqlclient

    python
    DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 数据库的连接类型 'NAME': 'app01', # 数据库的名字 'USER': 'root', # 数据库的登录账号 'PASSWORD': '', # 密码 'HOST': '127.0.0.1', # ip 'PORT': '3306', # 端口 } }
  • 在对应app的models.py文件中编写对应的表结构

    下面展示简单的表结构以及外键约束

    python
    class Department(models.Model): """ 部门表 """ title = models.CharField(max_length=32, verbose_name='部门名称') class Employee(models.Model): """ 员工表 """ name = models.CharField(max_length=16, verbose_name='姓名') password = models.CharField(max_length=64, verbose_name='密码') age = models.IntegerField(verbose_name='年龄') account = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='账户余额') created_at = models.DateTimeField(verbose_name='入职事件') # 外键约束 # 设置级联删除 department = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE) # 设置置空 # department = models.ForeignKey(to="Department", to_field="id", null=True, blank=True, on_delete=models.SET_NULL) # 设置选择,即确立简单的对应关系,由Django自动匹配 gender_choices = ( (1, "男"), (2, "女"), ) gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)

将编写好的表结构注册进数据库


  • 首先将数据库在MySQL中创建好
    sql
    create database your_database_name
  • 执行下列命令
    bash
    python manage.py makemigrations python manage.py migrate
  • 创建完成
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:回锅炒辣椒

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!