Search code examples
pythonflaskpaginationflask-admin

How to go to the last page of paginated Flask-Admin view by default.. without sorting descending


In Python's Flask-Admin for database table viewing/administrating, I need the view to open automatically to the last page of the paginated data.

Important: I cannot simply sort the records descending so the last record shows first.

Here's what my simple example below looks like. I'd like it to start on the last page, as pictured.

enter image description here

Here's some example code to reproduce my model:

import os
import os.path as op
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

import flask_admin as admin
from flask_admin.contrib.sqla import ModelView


# Create application
app = Flask(__name__)

# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'

# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db2.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)


# Models
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode(64))
    email = db.Column(db.Unicode(64))

    def __unicode__(self):
        return self.name


class UserAdmin(ModelView):
    column_searchable_list = ('name', 'email')
    column_editable_list = ('name', 'email')
    column_display_pk = True

    can_set_page_size = True
    # the number of entries to display in the list view
    page_size = 5


# Flask views
@app.route('/')
def index():
    return '<a href="/admin/">Click me to get to Admin!</a>'


admin = admin.Admin(app, 'Example', template_mode='bootstrap3')

# Add views
admin.add_view(UserAdmin(User, db.session))


def build_sample_db():
    """
    Populate a small db with some example entries.
    """

    db.drop_all()
    db.create_all()

    first_names = [
        'Harry', 'Amelia', 'Oliver', 'Jack', 'Isabella', 'Charlie','Sophie', 'Mia',
        'Jacob', 'Thomas', 'Emily', 'Lily', 'Ava', 'Isla', 'Alfie', 'Olivia', 'Jessica',
        'Riley', 'William', 'James', 'Geoffrey', 'Lisa', 'Benjamin', 'Stacey', 'Lucy'
    ]
    last_names = [
        'Brown', 'Smith', 'Patel', 'Jones', 'Williams', 'Johnson', 'Taylor', 'Thomas',
        'Roberts', 'Khan', 'Lewis', 'Jackson', 'Clarke', 'James', 'Phillips', 'Wilson',
        'Ali', 'Mason', 'Mitchell', 'Rose', 'Davis', 'Davies', 'Rodriguez', 'Cox', 'Alexander'
    ]

    for i in range(len(first_names)):
        user = User()
        user.name = first_names[i] + " " + last_names[i]
        user.email = first_names[i].lower() + "@example.com"
        db.session.add(user)

    db.session.commit()
    return


if __name__ == '__main__':

    # Build a sample db on the fly, if one does not exist yet.
    app_dir = op.realpath(os.path.dirname(__file__))
    database_path = op.join(app_dir, app.config['DATABASE_FILE'])
    if not os.path.exists(database_path):
        build_sample_db()

    # Start app
    app.run(debug=True, host='0.0.0.0')


Solution

  • I've found an ideal solution to my problem, overriding the index_view() template-rendering view method in the flask_admin.contrib.sqla.ModelView class.

    class UserAdmin(ModelView):
        column_searchable_list = ('name', 'email')
        column_editable_list = ('name', 'email')
        column_display_pk = True
    
        can_set_page_size = True
        # the number of entries to display in the list view
        page_size = 5
    
        # Now to override the index_view method
        @expose('/')
        def index_view(self):
            """List view overridden to DEFAULT to the last page, 
            if no other request args have been submitted"""
    
            if self.can_delete:
                delete_form = self.delete_form()
            else:
                delete_form = None
    
            # Grab parameters from URL
            view_args = self._get_list_extra_args()
    
            # Map column index to column name
            sort_column = self._get_column_by_idx(view_args.sort)
            if sort_column is not None:
                sort_column = sort_column[0]
    
            # Get page size
            page_size = view_args.page_size or self.page_size
    
            #####################################################################
            # Custom functionality to start on the last page instead of the first
    
            if len(request.args) == 0:
                # Standard request for the first page (no additional args)
                count_query = self.get_count_query() if not self.simple_list_pager else None
                # Calculate number of rows if necessary
                count = count_query.scalar() if count_query else None
                # Calculate number of pages
                if count is not None and page_size:
                    num_pages = int(ceil(count / float(page_size)))
                    # Change the page to the last page (minus 1 for zero-based counting)
                    setattr(view_args, 'page', num_pages - 1)
    
            # End of custom code. The rest below is from the Flask-Admin package
            ############################################################################
    
            # Get database data for the view_args.page requested
            count, data = self.get_list(view_args.page, sort_column, view_args.sort_desc,
                                        view_args.search, view_args.filters, page_size=page_size)
    
            list_forms = {}
            if self.column_editable_list:
                for row in data:
                    list_forms[self.get_pk_value(row)] = self.list_form(obj=row)
    
            # Calculate number of pages
            if count is not None and page_size:
                num_pages = int(ceil(count / float(page_size)))
            elif not page_size:
                num_pages = 0  # hide pager for unlimited page_size
            else:
                num_pages = None  # use simple pager
    
            # Various URL generation helpers
            def pager_url(p):
                # Do not add page number if it is first page
                if p == 0:
                    p = None
    
                return self._get_list_url(view_args.clone(page=p))
    
            def sort_url(column, invert=False, desc=None):
                if not desc and invert and not view_args.sort_desc:
                    desc = 1
    
                return self._get_list_url(view_args.clone(sort=column, sort_desc=desc))
    
            def page_size_url(s):
                if not s:
                    s = self.page_size
    
                return self._get_list_url(view_args.clone(page_size=s))
    
            # Actions
            actions, actions_confirmation = self.get_actions_list()
            if actions:
                action_form = self.action_form()
            else:
                action_form = None
    
            clear_search_url = self._get_list_url(view_args.clone(page=0,
                                                                  sort=view_args.sort,
                                                                  sort_desc=view_args.sort_desc,
                                                                  search=None,
                                                                  filters=None))
    
            return self.render(
                self.list_template,
                data=data,
                list_forms=list_forms,
                delete_form=delete_form,
                action_form=action_form,
    
                # List
                list_columns=self._list_columns,
                sortable_columns=self._sortable_columns,
                editable_columns=self.column_editable_list,
                list_row_actions=self.get_list_row_actions(),
    
                # Pagination
                count=count,
                pager_url=pager_url,
                num_pages=num_pages,
                can_set_page_size=self.can_set_page_size,
                page_size_url=page_size_url,
                page=view_args.page,
                page_size=page_size,
                default_page_size=self.page_size,
    
                # Sorting
                sort_column=view_args.sort,
                sort_desc=view_args.sort_desc,
                sort_url=sort_url,
    
                # Search
                search_supported=self._search_supported,
                clear_search_url=clear_search_url,
                search=view_args.search,
                search_placeholder=self.search_placeholder(),
    
                # Filters
                filters=self._filters,
                filter_groups=self._get_filter_groups(),
                active_filters=view_args.filters,
                filter_args=self._get_filters(view_args.filters),
    
                # Actions
                actions=actions,
                actions_confirmation=actions_confirmation,
    
                # Misc
                enumerate=enumerate,
                get_pk_value=self.get_pk_value,
                get_value=self.get_list_value,
                return_url=self._get_list_url(view_args),
    
                # Extras
                extra_args=view_args.extra_args,
            )