Flask 多个参数如何在从一个 url 查询数据库的多列时避免多个 if 语句

pas*_*ale 3 python sqlalchemy flask

我正在尝试使用flask作为前端构建一个会计数据库。主页面是账本,有九栏“日期”“描述”“借方”“贷方”“金额”“账户”“参考”“期刊”和“年份”,我需要每次都可以查询一次两个,有超过 8000 个条目,并且还在不断增加。到目前为止,我的代码显示了所有行,一次 200 行,分页,我读过“pep 8”,它谈论可读代码,我读过这个多个参数和这个多个参数,喜欢使用的想法

请求.args.get

但是我需要在查询之前显示所有行,我还查看了这个嵌套的 ifs,我想也许我可以为每个查询使用一个函数,并在视图函数之外使用“If”,然后在视图函数中调用每个,但我不知道该怎么做。或者我可以为每个查询设置一个视图函数。但我不确定这会如何工作,这是我目前的代码,

@bp.route('/books', methods=['GET', 'POST'])
@bp.route('/books/<int:page_num>', methods=['GET', 'POST'])
@bp.route('/books/<int:page_num>/<int:id>', methods=['GET', 'POST'])
@bp.route('/books/<int:page_num>/<int:id>/<ref>', methods=['GET', 'POST'])
@login_required
def books(page_num, id=None, ref=None):
    if ref is not None:
        books = Book.query.order_by(Book.date.desc()).filter(Book.REF==ref).paginate(per_page=100, page=page_num, error_out=True)
    else:
        books = Book.query.order_by(Book.date.desc()).paginate(per_page=100, page=page_num, error_out=True)
    if id is not None:
        obj = Book.query.get(id) or Book()
        form = AddBookForm(request.form, obj=obj)
        if form.validate_on_submit():
            form.populate_obj(obj)
            db.session.add(obj)
            db.session.commit()
            return redirect(url_for('books.books'))
    else:
        form = AddBookForm()
        if form.validate_on_submit():
            obj = Book(id=form.id.data, date=form.date.data, description=form.description.data, debit=form.debit.data,\
                  credit=form.credit.data, montant=form.montant.data, AUX=form.AUX.data, TP=form.TP.data,\
                      REF=form.REF.data, JN=form.JN.data, PID=form.PID.data, CT=form.CT.data)
            db.session.add(obj)
            db.session.commit()
            return redirect(url_for('books.books', page_num=1))
    return render_template('books/books.html', title='Books', books=books, form=form)
Run Code Online (Sandbox Code Playgroud)

使用此代码没有错误消息,这是一个问题,询问如何使我的代码保持可读性和尽可能简单,并能够查询数据库的九列,同时显示查询的所有行和所有行未激活任何查询 非常感谢所有帮助。保罗

我正在使用 python 3.7 在 debian 10 上运行它

编辑:我习惯于使用 Libre Office Base

我的问题是如何在我的数据库中一次搜索一两列,我希望能够搜索 12 列中的 9 列,我希望能够一次搜索一列或多列,例如: “参考”列用供应商名称“FILCUI”标记文档参考,如“A32”和“帐户”,可能同时标记。我进行了更多的研究,发现大多数人都提倡使用“全文”搜索引擎,例如“Elastic or Whoosh”,但就我而言,我觉得如果我搜索“A32”(文档编号),我会在模型中得到任何东西共 12 列,A 1 2。我看过Flask 教程 101 搜索 Whoosh所有非常好的教程,由优秀的人提供,

但鉴于 SQLAlchemy 不支持此功能,

我认为这个SQLAlchemy-Intergrations也不起作用。因此,有没有一种方法可以“搜索”“查询”“过滤”模型的多个不同列,每个搜索可能都有一个表单,而不会以“一袋结”之类的代码无法阅读或测试结束?如果可能的话,我想坚持使用 SQLAlchemy 我只需要一个指向正确方向的小指针或一个我可以测试的简单个人意见。温暖的问候。编辑:

我还没有回答我的问题,但我已经进步了,我可以一次查询一行并在一页上显示所有结果,没有一个“if”语句,我认为我的代码清晰可读(?)将每个查询分成自己的返回同一主页面的视图函数,每个函数都有自己的提交按钮。这使我能够呈现相同的页面。这是我的路线代码。

@bp.route('/search_aux', methods=['GET', 'POST'])
@login_required
def search_aux():
    page_num = request.args.get('page_num', default = 1, type = int)
    books = Book.query.order_by(Book.date.desc()).paginate(per_page=100, page=page_num, error_out=True)
    add_form = AddBookForm()
    aux_form = SearchAuxForm()
    date_form = SearchDateForm()
    debit_form = SearchDebitForm()
    credit_form = SearchCreditForm()
    montant_form = SearchMontantForm()
    jn_form = SearchJNForm()
    pid_form = SearchPIDForm()
    ref_form = SearchREForm()
    tp_form = SearchTPForm()
    ct_form = SearchCTForm()
    des_form = SearchDescriptionForm()
    if request.method == 'POST':
        aux = aux_form.selectaux.data
        books = Book.query.order_by(Book.date.desc()).filter(Book.AUX == str(aux)).paginate(per_page=100, page=page_num, error_out=True)
    return render_template('books/books.html', books=books, add_form=add_form, aux_form=aux_form, date_form=date_form, debit_form=debit_form,
                       credit_form=credit_form, montant_form=montant_form, jn_form=jn_form, pid_form=pid_form, ref_form=ref_form,
                        tp_form=tp_form, ct_form=ct_form, des_form=des_form)
Run Code Online (Sandbox Code Playgroud)

每个查询都有一个简单的形式,它适用于每个单个查询。这是表单和html代码:

class SearchAuxForm(FlaskForm):
    selectaux = QuerySelectField('Aux', query_factory=AUX, get_label='id')
    submitaux = SubmitField('submit')
def AUX():
    return Auxilliere.query

html:
   <div class="AUX">
        <form action="{{ url_for('books.search_aux') }}" method="post">
            {{ aux_form.selectaux(class="input") }}{{ aux_form.submitaux(class="submit") }}
        </form>
    </div>
Run Code Online (Sandbox Code Playgroud)

我试图通过一个提交按钮将其作为一个单一的功能来完成,但它以灾难告终。我没有提交这个作为答案,因为它没有完成我问的所有事情,但它是一个开始。

最终编辑:

我要感谢重新提出这个问题的人,让 Lucas Scott 先生提供了一个引人入胜且内容丰富的答案,以帮助我和其他人。

小智 6

有很多方法可以实现您想要的结果,即能够查询/过滤表中的多个列。我将给您一个示例,说明我将如何创建一个端点,该端点将允许您对一列或多列进行过滤。

这是我们的基本Books模型和/books作为存根的端点

import flask
from flask_sqlalchemy import SQLAlchemy


app = flask.Flask(__name__)
db = SQLAlchemy(app)  # uses in memory sqlite3 db by default

class Books(db.Model):
    __tablename__ = "book"

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(255), nullable=False)
    author = db.Column(db.String(255), nullable=False)
    supplier = db.Column(db.String(255))
    published = db.Column(db.Date, nullable=False)

db.create_all()


@app.routes("/books", methods=["GET"])
def all_books():
    pass
Run Code Online (Sandbox Code Playgroud)

第一步是决定使用 url 参数查询集合的方法。我将使用查询参数中相同键的多个实例作为列表给出的事实,以允许我们对多个列进行过滤。

例如/books?filter=id&filter=author将变成{"filter": ["id", "author"]}.

对于我们的查询语法,我们将使用逗号分隔值作为过滤器值。

例子:

books?filter=author,eq,jim&suplier,eq,self published

变成{"filter": ["author,eq,jim", "supplier,eq,self published"]}. 注意 中的空格self published。flask 会为我们处理 url-encoding 并返回一个带空格的字符串而不是%20.

让我们通过添加一个Filter类来表示我们的filter查询参数来清理一下。

class QueryValidationError(Exception):
    """ We can handle specific exceptions and 
        return a http response with flask """
    pass


class Filter:
    supported_operators = ("eq", "ne", "lt", "gt", "le", "ge")

    def __init__(self, column, operator, value):
        self.column = column
        self.operator = operator
        self.value = value
        self.validate()

    def validate(self):
        if operator not in self.supported_operators:
            # We will deal with catching this later
            raise QueryValidationError(
                f"operator `{operator}` is not one of supported "
                f"operators `{self.supported_operators}`"
            )
Run Code Online (Sandbox Code Playgroud)

现在我们将创建一个函数来将我们的过滤器列表处理成一个Filter对象列表。

def create_filters(filters):
    filters_processed = []
    if filters is None:
        # No filters given
        return filters_processed
    elif isinstance(filters, str):
        # if only one filter given
        filter_split = filters.split(",")
        filters_processed.append(
            Filter(*filter_split)
        )
    elif isinstance(filters, list):
        # if more than one filter given
        try:
            filters_processed = [Filter(*_filter.split(",")) for _filter in filters]
        except Exception:
            raise QueryValidationError("Filter query invalid")
    else:
        # Programer error
        raise TypeError(
            f"filters expected to be `str` or list "
            f"but was of type `{type(filters)}`"
        )

    return filters_processed
Run Code Online (Sandbox Code Playgroud)

现在我们可以将我们的辅助函数添加到我们的端点。

@app.route("/books", methods=["GET"])
def all_books():
    args = flask.request.args
    filters = create_filters(args.get("filter"))
Run Code Online (Sandbox Code Playgroud)

SQLAlchemy 允许我们使用运算符重载进行过滤。那就是使用filter(Book.author == "some value"). 在==这里不会触发默认==行为。相反,SQLAlchemy 的创建者重载了这个运算符,而是创建了检查相等性的 SQL 查询并将其添加到查询中。我们可以通过使用 Pythons运算符模块来利用这种行为。例如:

import operator
from models import Book

authors = Book.query.filter(operator.eq(Book.author, "some author")).all()
Run Code Online (Sandbox Code Playgroud)

这本身似乎没有帮助,但让我们更接近创建通用和动态过滤机制。使这更加动态的下一个重要步骤是使用内置函数,getattr它允许我们使用字符串查找给定对象的属性。例子:

class Anything:
    def say_hi(self):
        print("hello")

# use getattr to say hello
getattr(Anything, "say_hi")  # returns the function `say_hi`
getattr(Anything, "say_hi")()  # calls the function `say_hi`
Run Code Online (Sandbox Code Playgroud)

我们现在可以通过创建一个通用的过滤函数将这一切联系在一起:

def filter_query(filters, query, model):
    for _filter in filters:
        # get our operator
        op = getattr(operator, _filter.operator)
        # get the column to filter on
        column = getattr(model, _filter.column)
        # value to filter for
        value = _filter.value

        # build up a query by adding multiple filters
        query = query.filter(op(column, value))

    return query

Run Code Online (Sandbox Code Playgroud)

我们可以使用我们的实现过滤任何模型,而不仅仅是通过一列。

def filter_query(filters, query, model):
    for _filter in filters:
        # get our operator
        op = getattr(operator, _filter.operator)
        # get the column to filter on
        column = getattr(model, _filter.column)
        # value to filter for
        value = _filter.value

        # build up a query by adding multiple filters
        query = query.filter(op(column, value))

    return query

Run Code Online (Sandbox Code Playgroud)

这是所有内容,包括验证错误的错误处理

@app.route("/books", methods=["GET"])
def all_books():
    args = flask.request.args
    filters = create_filters(args.get("filter"))
    query = Books.query
    query = filter_query(filters, query, Books)

    result = []
    for book in query.all():
        result.append(dict(
            id=book.id,
            title=book.title,
            author=book.author,
            supplier=book.supplier,
            published=str(book.published)
        ))

    return flask.jsonify(result), 200
Run Code Online (Sandbox Code Playgroud)

我希望这能回答你的问题,或者给你一些关于如何解决你的问题的想法。

我还建议查看序列化和编组工具,例如marshmallow-sqlalchemy,这将帮助您简化将模型转换为 json 并再次返回的过程。它也有助于嵌套对象序列化,如果您要返回关系,这可能会很痛苦。