如何在Flask/SQLAlchemy中显示多对多查询结果的列

10 投票
4 回答
3992 浏览
提问于 2025-04-18 12:35

我正在学习Python/Flask/SQLAlchemy,想通过建立一个简单的维基百科来练习(这个维基百科主要参考了一个Flask-Admin的例子),但是我在理解如何显示我多对多关系中的新列时遇到了困难。

我已经成功创建了维基,并且为标签建立了一个多对多关系表,这个过程没有问题(根据我看到的,标签功能也正常),但我想把标签显示为一列,却一直搞不定逻辑。

目标:我想显示一列,里面展示多对多关联表中引用的标签。

下面是我想实现的效果的图片:

一张表格,里面有一个名为“标签”的列,包含两行。第一行的内容是“标签1, 标签4”。第二行的内容是“标签4, 标签5”。

这是我认为相关的代码:

wiki_tags_table = db.Table('wiki_tags', db.Model.metadata,
                           db.Column('wiki_id', db.Integer, db.ForeignKey('wiki.id')),
                           db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
                           )

class Wiki(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), unique=True)
    description = db.Column(db.Text)
    path = db.Column(db.Unicode(256))
    date_added = db.Column(db.DateTime)
    tags_id = db.Column(db.Integer, db.ForeignKey('tag.id'))
    tags = db.relationship('Tag', secondary=wiki_tags_table, backref=db.backref('wiki_tags_table', lazy='dynamic'))

    def __unicode__(self):
        return self.item

class WikiAdmin(sqla.ModelView):

    column_exclude_list = ['path']

    column_hide_backrefs = False

    form_overrides = {
        'path': form.FileUploadField
    }

    form_args = {
        'path': {
            'label': 'File',
            'base_path': file_path
        }
    }

    column_searchable_list = ('title', 'description', 'path')

    def __init__(self, session):
        super(WikiAdmin, self).__init__(Wiki, session)

class Tag(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(64))

    def __unicode__(self):
        return self.name

我一直在参考这些文档(主要是尝试backref的不同变体),但还没有找到解决办法:

4 个回答

0

试试我做的这个方法,给多对多的字段使用一个特别的列格式化器。这对我来说是有效的。


def many_to_many_formatter(view, context, model, name: str) -> str:
    """This function formats the many-to-many fields for display in the list view"""
    try:
        rel_field, col_name = name.split(".")
        list_of_tags: list = getattr(model, rel_field)
        list_of_results: list = [str(getattr(tag, col_name)) for tag in list_of_tags]
        new_str: str = ", ".join(list_of_results)
    except Exception:
        current_app.logger.exception("Error trying to format many-to-many relationship field in list view!")
        new_str = ""

    return new_str


class SpecialView(ModelView):
    """Flask-Admin view"""

    column_list = (
        "model",
        "description",
        # many-to-many field
        "parts_rel.part_num"
    )

    column_formatters = {
        "parts_rel.part_num": many_to_many_formatter,
    }
0

我刚才也遇到了同样的问题,解决办法就是在ViewModel类里添加一个column_list字段,所以在这个情况下,代码是这样的:

class WikiAdmin(sqla.ModelView):

    ...
    column_list = ('title', 'description', 'dataadded', 'tags')

这个问题虽然是老问题,但这是我在自己寻找解决办法时遇到的第一个(也可以说是唯一相关的)讨论,所以希望这个回答能帮到某个人,节省一些时间。

0

关系属性会把相关的表加载成一个对象列表,所以你可以在视图中像这样打印出所有的维基条目和所有相关的标签:

for wiki_item in Wiki.query.all():
    print wiki_item.title
    for tag in wiki_item.tags:
        print tag.name 

如果你这样做,会发生什么呢?

1

我不确定这是否有帮助,因为我自己也在学习。不过我遇到过类似的问题,我想从一个“外键表”中显示一列数据,最后是这样做的:

我的 modle.py 文件

  from app import db

class Member(db.Model):
    __tablename__ = 'members'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(64), index=True)
    phone = db.Column(db.String(10), index=True)
    email = db.Column(db.String(120), index=True, unique=True)
    grade = db.relationship('Grade', backref='member')
    attendance = db.relationship('Attendance', backref='member')

    def __repr__(self):
        return '<User %r>' % self.name


class Grade(db.Model):
    __tablename__ = 'grades'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    member_id = db.Column(db.Integer, db.ForeignKey('members.id'))
    grade = db.Column(db.String(10))
    grade_date = db.Column(db.Date)

    def __repr__(self):
        return '<Grading %r>' % self.id

    def __str__(self):
        return self.grade

    def __unicode__(self):
        return self.grade


class Attendance(db.Model):
    __tablename__ = 'attendance'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    id_member = db.Column(db.Integer, db.ForeignKey('members.id'))
    attend_date = db.Column(db.Date)

    def __repr__(self):
        return '<Attenance %r>' % self.id

我的 views.py 文件

from app.models import Member, Grade, Attendance
from app import app, admin, db
from flask_admin import BaseView, expose
from flask_admin.contrib.fileadmin import FileAdmin
from flask_admin.contrib.sqla import ModelView
import os.path as op


class AdminView(ModelView):
    column_display_pk = True  # optional, but I like to see the IDs in the list
    column_hide_backrefs = False
    # column_list = ('id', 'name', 'parent')
    create_modal = True
    edit_modal = True


class MemberAdmin(ModelView):
    column_display_pk = True  # optional, but I like to see the IDs in the list
    column_hide_backrefs = False
    can_view_details = True
    create_modal = True
    edit_modal = True
    form_columns = ['name', 'phone', 'email', 'grade', 'attendance']
    column_details_list = ['name', 'phone', 'email', 'grade', 'attendance']
    column_searchable_list = ['name', 'email']
    column_list = ('id', 'name', 'phone','email','grade')


class GradeAdmin(ModelView):
    column_display_pk = True  # optional, but I like to see the IDs in the list
    column_hide_backrefs = False
    column_list = ('id', 'member', 'grade', 'grade_date')
    form_choices = {'grade': [('Beginner', 'Beginner'), ('Yellow', 'Yellow'), ('Orange', 'Orange'),
                              ('Green 1', 'Green 1'), ('Green 2', 'Green 2'), ('Blue 1', 'Blue 1'),
                              ('Blue 2', 'Blue 2'), ('Purple 1', 'Purple 1'), ('Purple 2', 'Purple 2'),
                              ('Brown 1', 'Brown 1'), ('Brown 2', 'Brown 2'), ('Red 1', 'Red 1')]}


admin.add_view(MemberAdmin(Member, db.session, endpoint='member', category='Admin'))
admin.add_view(GradeAdmin(Grade, db.session, endpoint='grades', category='Admin'))
admin.add_view(ModelView(Attendance, db.session, endpoint='attendance', category='Admin'))

由于我还不是很懂这些(还在学习中),我觉得让我在 Member 模型中看到额外一列(来自 Grade 模型)的关键在于 MemberAdmin 类中的这些代码:

column_hide_backrefs = False
can_view_details = True
...
form_columns = ['name', 'phone', 'email', 'grade', 'attendance']

撰写回答