Search code examples
pythonflaskflask-admin

Flask-Admin: Change sort order of inline_models?


In Flask-Admin, is there any way to control the order of the list generated by inline_models? It seems to be coming out in database order, i.e. ordered by the ID primary key.

That is, if I have an Author that has_many Books, and my AuthorModelView class has inline_models = (Books,), the books are always ordered by book_id. Passing column_default_sort to the inline model, to try to sort by (say) title or date_purchased, has no effect. Is there any way to handle this?


Solution

  • enter image description here

    Specify the order_by parameter when specifying the relationships, see docs. See note at the end if you want to sort by a specific field at runtime.

    Example of model declarations for Author -> Books. Here we are ordering on the book title field ascending - order_by='Book.title.asc()' :

    class Author(db.Model):
        __tablename__ = 'authors'
    
        id = db.Column(db.Integer, primary_key=True)
        first_name = db.Column(db.Text(length=255), nullable=False)
        last_name = db.Column(db.Text(length=255), nullable=False)
        books = db.relationship("Book", order_by='Book.title.asc()', cascade="all,delete-orphan", backref=db.backref('author'))
    
        def __str__(self):
            return f"ID: {self.id}; First Name: {self.first_name}; Last Name: {self.last_name}"
    
    
    class Book(db.Model):
    
        __tablename__ = 'books'
    
        id = db.Column(db.Integer, primary_key=True)
        author_id = db.Column(db.Integer, db.ForeignKey('authors.id'), nullable=False, index=True)
        title = db.Column(db.Text(length=255), nullable=False)
    
        def __str__(self):
            return f"ID: {self.id}; Title: {self.title}; Author ID: {self.author_id}"
    

    Single file full example:

    from faker import Faker
    import click
    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    from flask_admin import Admin
    from flask_admin.contrib import sqla
    
    db = SQLAlchemy()
    
    
    class Author(db.Model):
        __tablename__ = 'authors'
    
        id = db.Column(db.Integer, primary_key=True)
        first_name = db.Column(db.Text(length=255), nullable=False)
        last_name = db.Column(db.Text(length=255), nullable=False)
        books = db.relationship("Book", order_by='Book.title.asc()', cascade="all,delete-orphan", backref=db.backref('author'))
    
        def __str__(self):
            return f"ID: {self.id}; First Name: {self.first_name}; Last Name: {self.last_name}"
    
    
    class Book(db.Model):
    
        __tablename__ = 'books'
    
        id = db.Column(db.Integer, primary_key=True)
        author_id = db.Column(db.Integer, db.ForeignKey('authors.id'), nullable=False, index=True)
        title = db.Column(db.Text(length=255), nullable=False)
    
        def __str__(self):
            return f"ID: {self.id}; Title: {self.title}; Author ID: {self.author_id}"
    
    
    app = Flask(__name__)
    
    app.config['SECRET_KEY'] = '123456790'
    app.config['SQLALCHEMY_ECHO'] = True
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sample.sqlite'
    
    db.init_app(app)
    
    
    @app.cli.command('create-database', short_help='Create Authors database')
    @click.option('--count', default=100, help='Number of authors (default 100)')
    def create_database(count):
    
        """
            Create database with "count" authors
        """
    
        db.drop_all()
        db.create_all()
        _faker = Faker()
        for _ in range(0, count):
            _author = Author(
                first_name=_faker.first_name(),
                last_name=_faker.last_name(),
            )
            db.session.add(_author)
            for _ in range(0, _faker.pyint(1, 20)):
                _book = Book(
                    title=_faker.sentence(),
                    author=_author
                )
                db.session.add(_book)
    
        db.session.commit()
    
    
    class AuthorView(sqla.ModelView):
    
        # default sort: last_name ascending
        column_default_sort = ('last_name', False)
    
        inline_models = (Book,)
    
    
    # Flask views
    @app.route('/')
    def index():
        return '<a href="/admin/">Click me to get to Admin!</a>'
    
    
    admin = Admin(app, template_mode="bootstrap3")
    admin.add_view(AuthorView(Author, db.session))
    
    
    if __name__ == '__main__':
        app.run()
    

    Run the following command to initialize an SQLite DB.

    flask create-database --count 100
    

    If you want to change the sort field at runtime override the view's get_one() method and use Python to sort the instrumented list directly. For example, sorting by ISBN field instead of title:

    class Author2View(sqla.ModelView):
        def get_one(self, id):
            _author = super().get_one(id)
            _author.books = sorted(_author.books, key=lambda book: book.isbn)
            return _author
    
        # default sort: last_name ascending
        column_default_sort = ('last_name', False)
    
        inline_models = (Book,)
    
    admin.add_view(Author2View(Author, db.session, name="Author 2", endpoint='author-2'))