flask sqlalchemy wtf 使用笔记

本文详细记录了使用 Flask-SQLAlchemy 构建数据库模型时遇到的问题和解决方案,包括VIP用户模型、一对多关系映射、延迟加载(lazy)设置、deferred 列定义以及多对多关系的实现。同时还分享了如何处理连接字符串中的特殊字符和确保创建表的顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天在搭建 Flask-SQLAlchemy 的demo时碰到了一些小的坑,记录下来,防止以后再范,本文将持续更新,记录一些Flask-SQLAlchemy 的使用小提示

对于在windows下无法使用pip 安装 mysql-python模块 和 flask-mysql模块的同学,请到这里下载32位或者64位的安装包进行安装 http://pan.baidu.com/s/1qWz7I5e

1、封装db对象
Flask封装了一个db对象,官方示例如下:

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)
#your code

但是在实际使用过程中发现,当Modal有多个文件时db对象每次都是一个新的实例,无法使用 db.create_all() 来一起创建数据表和使用db.session操作数据对象,于是我这样处理了下,不知道对不对
在Modals文件夹中,创建一个 dbModal.py 文件,用来保存 db实例,代码如下:

# -*- coding: utf-8 -*-
from datetime import datetime
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from restapi import app

db = SQLAlchemy(app)

#这样其他 Modals 和需要数据库访问的文件只要如下引入 db 实例就可以了

from restapi.models.dbModel import db

2、一对多的Modal定义:
经常会碰到这样的场景,比如有用户表:Plat_User(平台用户),还有一个表用来存储用户的多个备选头像:Plat_User_Face(平台用户头像表),这就形成了One(用户表)to Many(头像表),我们可以如下分别定义两张表即可:

from datetime import datetime
from flask import Flask

from flask.ext.sqlalchemy import SQLAlchemy
from restapi import app
from restapi.models.dbModel import db
from restapi.models.faceModel import *
from restapi.bussiness.UtilsBl import DumpToDict
import json

vip用户的model

class PlatUser(db.Model, DumpToDict):
    #表名
    __tablename__ = 'Plat_User'

    Id = db.Column(db.Integer, primary_key=True)
    UserName = db.Column(db.String(50))
    Password = db.Column(db.String(50))
    UserTips = db.deferred(db.Column(db.Text))
    WriteTime = db.Column(db.DateTime, onupdate=datetime.now, default=datetime.now)
    Faces = db.relationship('PlatUserFace', backref='Plat_User', lazy='select')

    def __init__(self, UserName=None, Password=None):
        self.UserName = UserName
        self.Password = Password

接下来定义Face表:

# -*- coding: utf-8 -*-
from datetime import datetime
from flask import Flask

from flask.ext.sqlalchemy import SQLAlchemy
from restapi import app
from restapi.models.dbModel import *
from restapi.models.UsersModel import *
from restapi.bussiness.UtilsBl import DumpToDict
import json

vip用户的model

class PlatUserFace(db.Model, DumpToDict):
    #表名
    __tablename__ = 'Plat_User_Face'

    Id = db.Column(db.Integer, primary_key=True)
    FaceUrl = db.Column(db.String(50))
    FaceName = db.Column(db.String(50))
    WriteTime = db.Column(db.DateTime, onupdate=datetime.now, default=datetime.now)
    UserId = db.Column(db.Integer, db.ForeignKey('Plat_User.Id'), nullable=False)


    def __init__(self, UserId=None, FaceUrl=None, FaceName=None):
        self.UserId = UserId
        self.FaceUrl = FaceUrl
        self.FaceName = FaceName

这样我们就将表 ‘Plat_User’ 和表 ‘Plat_User_Face’ 进行了一对多的关系映射
注意这边有一个小坑,在定义 Plat_user 表 Faces 时

Faces = db.relationship(‘PlatUserFace’, backref=’Plat_User’, lazy=’select’)

这边 PlatUserFace 一定要写类名,写表名会报错!!!

3、关于 lazy 的设置
lazy设置表示这个属性等到用户使用到才去加载,在我们打开 SQLAlchemy 的调试加断点后,可以清晰的看到,第一次查询和第二次查询的语句,lazy也有几种不通设置,如下:

(1)’select’ (默认值)意味着 SQLAlchemy 会在使用一个标准 select 语句 时一气呵成加载那些数据
(2)’joined’ 让 SQLAlchemy 当父级使用 JOIN 语句是,在相同的查询中加 载关系。
(3)’subquery’ 类似 ‘joined’ ,但是 SQLAlchemy 会使用子查询。
(4)’dynamic’ 在你有可能条目时是特别有用的。 SQLAlchemy 会返回另一个查 询对象,你可以在加载这些条目时进一步提取。如果不仅想要关系下的少量条目 时,这通常是你想要的。

这里主要说明一下 dynamic 这个设置,这个设置会在查询返回的结果中保留此字段为一个查询实例,可以用如下代码说明:

获取用户信息

sqlData = PlatUser.query.options(lazyload(PlatUser.Faces)).filter_by(Id=user.Id).all()

这里需要.all() 才能手动的去获取此用户Face的列表

FaceList = sqlData.Faces.all()

当然一般使用 select 就足够了

4、deferred列定义
有些字段内容比较多而且不经常使用,比如是一个新闻的内容字段,在列表页或者封面页我们完全不必读取这个大的字段到内存里,这时候我们就可以设定这个列为 deferred,如下:

UserTips = db.deferred(db.Column(db.Text))

通常在查询时会不包括这个字段,当然我们也可以手动指定某一个字段不加载,例如:

user.sqlData = PlatUser\
            .query\
            .options(defer('Password'))\
            .filter_by(Id=self.userId).first()

如果想要一次性加载 UserTips 这个字段,则:

user.sqlData = PlatUser\
            .query\
            .options(undefer('UserTips'))\
            .filter_by(Id=self.userId).first()

5、多对多的定义:
有这样的需求,用户表和兴趣表,一个用户可以拥有多个兴趣爱好,同时一个兴趣爱好,也能被多个用户拥有,这里还需要支持反向查找的功能,不仅可以通过用户的userid查找到他的兴趣爱好,还可以通过一个兴趣爱好,查找那些有这些兴趣爱好的人
Model我们这样定义:

vip用户的model

class PlatUser(db.Model, DumpToDict):
    #表名
    __tablename__ = 'Plat_User'

    Id = db.Column(db.Integer, primary_key=True)
    UserName = db.Column(db.String(50))
    Password = db.Column(db.String(50))
    UserTips = db.deferred(db.Column(db.Text))
    WriteTime = db.Column(db.DateTime, onupdate=datetime.now, default=datetime.now)
    Faces = db.relationship('PlatUserFace', backref='Plat_User', lazy='select')
    Hobbies = db.relationship('PlatUserHobby', secondary='Plat_User_Hobby_Relation',
        backref=db.backref('Plat_User', lazy='select'), lazy='select')

    def __init__(self, UserName=None, Password=None):
        self.UserName = UserName
        self.Password = Password

关联表

class UserHobby(db.Model):
    __tablename__ = 'Plat_User_Hobby_Relation'

    Id = db.Column(db.Integer, primary_key=True)
    UserId = db.Column(db.Integer, db.ForeignKey('Plat_User.Id'), nullable=False)
    HobbyId = db.Column(db.Integer, db.ForeignKey('Plat_User_Hobby.Id'), nullable=False)
    db.UniqueConstraint('UserId', 'HobbyId', name='unique_1')

    def __init__(self, UserId, HobbyId):
        self.UserId = UserId
        self.HobbyId = HobbyId

对于兴趣表,我们根本无需特别的定义,因为在user表我们已经定义了 backref

用户兴趣爱好model

class PlatUserHobby(db.Model, DumpToDict):
    #表名
    __tablename__ = 'Plat_User_Hobby'

    Id = db.Column(db.Integer, primary_key=True)
    HobbyName = db.Column(db.String(50))
    WriteTime = db.Column(db.DateTime, onupdate=datetime.now, default=datetime.now)

    def __init__(self, UserId=None, HobbyName=None):
        self.UserId = UserId
        self.HobbyName = HobbyName

在使用时,和一对多一样,只需要访问User类的Hobbies属性即可
对于反向查找,只需要如下代码:

userHobby.sqlData = PlatUserHobby.query.filter_by(Id=hobbyId).first()
userList = userHobby.sqlData.Plat_User
#注意最后.Plat_User即可获得平台用户拥有这个属性的列表

6、连接字符串中密码包含 ‘@’ 等冲突字符处理
在sqlalchemy中,如果连接字符串包含 @ 等特殊字符,直接放在连接字符串中是无法连接到数据库的,可以通过如下的方式转义之后再连接

from urllib import quote_plus as urlquote
'mysql://用户名:{0}@ip地址/数据库名'.format(urlquote('坏密码'))

7、如果确定创建表的顺序
有时候我们回复发现定义好的表怎么也创建不了,报错说XX表没有找到。
这可能存在多个表互相关联的情况,这时候我们必须先创建表B,才能创建表A和C,保证他们创建顺序的方法很简单:

优先 import B,然后再import A, C ,这样就可以保证表B先create table,然后再create A, C了

http://snoopyxdy.blog.163.com/blog/static/6011744020149303282942/

<think>好的,我现在需要帮助用户在Flask中实现共享文件功能,并且点击文本文件时显示内容。首先,我需要回顾用户之前的提问和已有的引用内容,看看有哪些信息可以利用。 用户之前的问题是关于优化上传目录的功能,涉及分片上传、断点续传、目录结构等。现在他想在Flask中实现文件共享,并且点击文本文件显示内容。根据提供的引用,引用[1]提到使用Flask-WTF处理文件上传,引用[2]提到了Flask的instance folder和读取文件的方法,引用[3]是关于学习笔记共享平台的设计,可能涉及到文件管理和展示,引用[4]则关于表单验证。 首先,文件共享需要让用户能够上传文件,并列出已共享的文件。这涉及到文件上传、存储和展示。用户点击文本文件时显示内容,需要路由来处理文件内容的读取和展示。 接下来,我需要考虑如何构建Flask应用的结构。可能需要以下步骤: 1. **文件上传功能**:使用Flask-WTF的表单处理文件上传,如引用[1]中的示例。需要创建一个表单,包含文件上传字段和提交按钮。上传的文件需要保存到服务器的特定目录,比如使用instance文件夹(引用[2]提到的instance_path)或者其他自定义路径。 2. **文件存储与管理**:确定上传文件的存储位置。可以按照用户或日期组织目录结构,如用户之前提到的优化方案中的目录结构。这里可能需要考虑如何安全地存储文件,避免文件名冲突,以及权限管理。 3. **列出共享文件**:创建一个页面展示所有已上传的文件,每个文件作为一个链接。需要扫描存储目录中的文件,并生成对应的URL。 4. **显示文本文件内容**:当用户点击文本文件链接时,触发一个路由,读取文件内容并渲染到模板中。需要判断文件是否为文本类型(如.txt, .md等),如果是则读取内容,否则可能提供下载。 5. **安全性考虑**:确保上传的文件不会导致安全问题,比如限制文件类型,检查文件名,防止路径遍历攻击。引用[4]提到需要在服务器端验证数据,即使客户端已经做了验证,这一点很重要。 接下来,需要处理具体的代码实现。例如,使用Flask的send_file或send_from_directory来发送文件,但用户需要显示文本内容而不是下载,因此需要读取文件内容并传递给模板显示。 可能的问题点包括:如何安全地处理用户上传的文件名,防止路径遍历;如何判断文件是否为文本类型;如何高效读取和显示大文本文件等。 另外,根据用户之前的优化需求,可能还需要考虑分片上传或并发控制,但用户当前的问题可能更基础,先实现基本功能再优化。 在模板设计方面,需要一个上传表单和一个文件列表页面,每个文件条目链接到查看内容的路由。查看内容的页面需要显示文件名和内容。 总结步骤: - 创建上传表单,处理文件保存。 - 创建路由显示文件列表。 - 创建路由处理文件内容展示,读取文本文件内容并渲染。 - 添加安全性措施,如文件类型检查、文件名清理等。 现在需要将这些步骤转化为具体的代码示例,并参考已有的引用内容,比如使用Flask-WTF的表单,利用instance_path等。同时,要注意引用相关的参考文献,如处理文件上传使用引用[1]的方法,读取文件使用引用[2]的实例路径方法,验证使用引用[4]的服务器端验证。</think>--- ### Flask文件共享功能实现步骤 #### 1. **文件上传模块** - **表单定义**:使用Flask-WTF创建文件上传表单(参考引用[^1]) ```python from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed from wtforms import SubmitField class FileUploadForm(FlaskForm): file = FileField('选择文件', validators=[FileAllowed(['txt', 'md'], '仅支持文本文件')]) submit = SubmitField('上传') ``` - **上传处理**:保存文件到`instance/uploads`目录(参考引用[^2]) ```python import os from flask import current_app def save_uploaded_file(file): upload_dir = os.path.join(current_app.instance_path, 'uploads') os.makedirs(upload_dir, exist_ok=True) file_path = os.path.join(upload_dir, file.filename) file.save(file_path) return file_path ``` #### 2. **文件列表展示** - **路由定义**:扫描上传目录并返回文件列表 ```python @app.route('/files') def file_list(): upload_dir = os.path.join(current_app.instance_path, 'uploads') files = os.listdir(upload_dir) return render_template('file_list.html', files=files) ``` - **模板示例**(`file_list.html`): ```html <ul> {% for file in files %} <li><a href="{{ url_for('show_file', filename=file) }}">{{ file }}</a></li> {% endfor %} </ul> ``` #### 3. **文本内容展示** - **安全读取文件**:使用`open_instance_resource`方法(参考引用[^2]) ```python @app.route('/file/<filename>') def show_file(filename): safe_filename = secure_filename(filename) # 防止路径遍历 try: with current_app.open_instance_resource(f'uploads/{safe_filename}') as f: content = f.read().decode('utf-8') return render_template('show_file.html', filename=safe_filename, content=content) except FileNotFoundError: abort(404) ``` - **展示模板**(`show_file.html`): ```html <h2>{{ filename }}</h2> <pre>{{ content }}</pre> ``` #### 4. **安全增强措施** - **文件名消毒**:使用Werkzeug的`secure_filename` ```python from werkzeug.utils import secure_filename # 上传时处理文件名 filename = secure_filename(file.filename) ``` - **文件类型验证**:结合客户端和服务端验证(参考引用[^4]) ```python # 路由中补充验证 if not allowed_file(filename): flash('文件类型不支持') return redirect(request.url) ``` #### 5. **完整路由示例** ```python from flask import Flask, render_template, request, abort app = Flask(__name__) app.config['SECRET_KEY'] = 'your_secret_key' @app.route('/upload', methods=['GET', 'POST']) def upload_file(): form = FileUploadForm() if form.validate_on_submit(): file = form.file.data if allowed_file(file.filename): save_uploaded_file(file) return redirect(url_for('file_list')) return render_template('upload.html', form=form) ``` --- ### 关键问题解决方案 1. **路径安全**:通过`secure_filename`过滤特殊字符 ```python >>> secure_filename("../../etc/passwd.txt") 'etc_passwd.txt' ``` 2. **大文件处理**:流式读取避免内存溢出 ```python def read_large_file(path): with open(path, 'r', encoding='utf-8') as f: while chunk := f.read(4096): yield chunk ``` --- ### 运行效果验证 1. 访问`/upload`上传文本文件 2. 访问`/files`查看文件列表 3. 点击文件名查看内容渲染 --- ### 进阶优化方向 1. **文件分页显示**:使用Flask-SQLAlchemy记录文件元数据 2. **权限控制**:结合用户系统实现私有/公开文件管理(参考引用[^3]) 3. **版本控制**:记录文件修改历史 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值