Commit a51713a0 authored by Serge S. Koval's avatar Serge S. Koval

Fixed #897 - Ability to disable count queries and have back/fwd pager

parent 14dec4f5
...@@ -484,7 +484,7 @@ class ModelView(BaseModelView): ...@@ -484,7 +484,7 @@ class ModelView(BaseModelView):
query = self._search(query, search) query = self._search(query, search)
# Get count # Get count
count = query.count() count = query.count() if not self.simple_list_pager else None
# Sorting # Sorting
if sort_column: if sort_column:
......
...@@ -339,7 +339,7 @@ class ModelView(BaseModelView): ...@@ -339,7 +339,7 @@ class ModelView(BaseModelView):
query = f.apply(query, f.clean(value)) query = f.apply(query, f.clean(value))
# Get count # Get count
count = query.count() count = query.count() if not self.simple_list_pager else None
# Apply sorting # Apply sorting
if sort_column is not None: if sort_column is not None:
......
...@@ -222,7 +222,7 @@ class ModelView(BaseModelView): ...@@ -222,7 +222,7 @@ class ModelView(BaseModelView):
query = self._search(query, search) query = self._search(query, search)
# Get count # Get count
count = self.coll.find(query).count() count = self.coll.find(query).count() if not self.simple_list_pager else None
# Sorting # Sorting
sort_by = None sort_by = None
......
...@@ -4,7 +4,7 @@ import warnings ...@@ -4,7 +4,7 @@ import warnings
from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import desc from sqlalchemy.sql.expression import desc
from sqlalchemy import Column, Boolean, func, or_ from sqlalchemy import Boolean, func, or_
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from flask import flash from flask import flash
...@@ -765,7 +765,7 @@ class ModelView(BaseModelView): ...@@ -765,7 +765,7 @@ class ModelView(BaseModelView):
joins = set() joins = set()
query = self.get_query() query = self.get_query()
count_query = self.get_count_query() count_query = self.get_count_query() if not self.simple_list_pager else None
# Ignore eager-loaded relations (prevent unnecessary joins) # Ignore eager-loaded relations (prevent unnecessary joins)
# TODO: Separate join detection for query and count query? # TODO: Separate join detection for query and count query?
...@@ -781,6 +781,8 @@ class ModelView(BaseModelView): ...@@ -781,6 +781,8 @@ class ModelView(BaseModelView):
for table in self._search_joins: for table in self._search_joins:
if table.name not in joins: if table.name not in joins:
query = query.outerjoin(table) query = query.outerjoin(table)
if count_query is not None:
count_query = count_query.outerjoin(table) count_query = count_query.outerjoin(table)
joins.add(table.name) joins.add(table.name)
...@@ -795,6 +797,8 @@ class ModelView(BaseModelView): ...@@ -795,6 +797,8 @@ class ModelView(BaseModelView):
stmt = tools.parse_like_term(term) stmt = tools.parse_like_term(term)
filter_stmt = [c.ilike(stmt) for c in self._search_fields] filter_stmt = [c.ilike(stmt) for c in self._search_fields]
query = query.filter(or_(*filter_stmt)) query = query.filter(or_(*filter_stmt))
if count_query is not None:
count_query = count_query.filter(or_(*filter_stmt)) count_query = count_query.filter(or_(*filter_stmt))
# Apply filters # Apply filters
...@@ -811,15 +815,20 @@ class ModelView(BaseModelView): ...@@ -811,15 +815,20 @@ class ModelView(BaseModelView):
for table in join_tables: for table in join_tables:
if table.name not in joins: if table.name not in joins:
query = query.join(table) query = query.join(table)
if count_query is not None:
count_query = count_query.join(table) count_query = count_query.join(table)
joins.add(table.name) joins.add(table.name)
# turn into python format with .clean() and apply filter # turn into python format with .clean() and apply filter
query = flt.apply(query, flt.clean(value)) query = flt.apply(query, flt.clean(value))
if count_query is not None:
count_query = flt.apply(count_query, flt.clean(value)) count_query = flt.apply(count_query, flt.clean(value))
# Calculate number of rows # Calculate number of rows if necessary
count = count_query.scalar() count = count_query.scalar() if count_query else None
# Auto join # Auto join
for j in self._auto_joins: for j in self._auto_joins:
......
...@@ -321,6 +321,12 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -321,6 +321,12 @@ class BaseModelView(BaseView, ActionsMixin):
Controls if the primary key should be displayed in the list view. Controls if the primary key should be displayed in the list view.
""" """
simple_list_pager = False
"""
Enable or disable simple list pager.
If enabled, model interface would not run count query and will only show prev/next pager buttons.
"""
form = None form = None
""" """
Form class. Override if you want to use custom form for your model. Form class. Override if you want to use custom form for your model.
...@@ -1486,9 +1492,12 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1486,9 +1492,12 @@ class BaseModelView(BaseView, ActionsMixin):
view_args.search, view_args.filters) view_args.search, view_args.filters)
# Calculate number of pages # Calculate number of pages
if count is not None:
num_pages = count // self.page_size num_pages = count // self.page_size
if count % self.page_size != 0: if count % self.page_size != 0:
num_pages += 1 num_pages += 1
else:
num_pages = None
# Various URL generation helpers # Various URL generation helpers
def pager_url(p): def pager_url(p):
...@@ -1531,6 +1540,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1531,6 +1540,7 @@ class BaseModelView(BaseView, ActionsMixin):
pager_url=pager_url, pager_url=pager_url,
num_pages=num_pages, num_pages=num_pages,
page=view_args.page, page=view_args.page,
page_size=self.page_size,
# Sorting # Sorting
sort_column=view_args.sort, sort_column=view_args.sort,
......
...@@ -76,6 +76,31 @@ ...@@ -76,6 +76,31 @@
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}
{% macro simple_pager(page, have_next, generator) -%}
<div class="pagination">
<ul>
{% if page > 0 %}
<li>
<a href="{{ generator(page - 1) }}">&lt;</a>
</li>
{% else %}
<li class="disabled">
<a href="{{ generator(0) }}">&lt;</a>
</li>
{% endif %}
{% if have_next %}
<li>
<a href="{{ generator(page + 1) }}">&gt;</a>
</li>
{% else %}
<li class="disabled">
<a href="{{ generator(page) }}">&gt;</a>
</li>
{% endif %}
</ul>
</div>
{%- endmacro %}
{# ---------------------- Forms -------------------------- #} {# ---------------------- Forms -------------------------- #}
{% macro render_field(form, field, kwargs={}, caller=None) %} {% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
{% block model_menu_bar %} {% block model_menu_bar %}
<ul class="nav nav-tabs actions-nav"> <ul class="nav nav-tabs actions-nav">
<li class="active"> <li class="active">
<a href="javascript:void(0)">{{ _gettext('List') }} ({{ count }})</a> <a href="javascript:void(0)">{{ _gettext('List') }}{% if count %} ({{ count }}){% endif %}</a>
</li> </li>
{% if admin_view.can_create %} {% if admin_view.can_create %}
<li> <li>
...@@ -147,7 +147,13 @@ ...@@ -147,7 +147,13 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% block list_pager %}
{% if num_pages is not none %}
{{ lib.pager(page, num_pages, pager_url) }} {{ lib.pager(page, num_pages, pager_url) }}
{% else %}
{{ lib.simple_pager(page, data|length == page_size, pager_url) }}
{% endif %}
{% endblock %}
{% endblock %} {% endblock %}
{{ actionlib.form(actions, get_url('.action_view')) }} {{ actionlib.form(actions, get_url('.action_view')) }}
......
...@@ -74,6 +74,29 @@ ...@@ -74,6 +74,29 @@
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}
{% macro simple_pager(page, have_next, generator) -%}
<ul class="pagination">
{% if page > 0 %}
<li>
<a href="{{ generator(page - 1) }}">&lt;</a>
</li>
{% else %}
<li class="disabled">
<a href="{{ generator(0) }}">&lt;</a>
</li>
{% endif %}
{% if have_next %}
<li>
<a href="{{ generator(page + 1) }}">&gt;</a>
</li>
{% else %}
<li class="disabled">
<a href="{{ generator(page) }}">&gt;</a>
</li>
{% endif %}
</ul>
{%- endmacro %}
{# ---------------------- Forms -------------------------- #} {# ---------------------- Forms -------------------------- #}
{% macro render_field(form, field, kwargs={}, caller=None) %} {% macro render_field(form, field, kwargs={}, caller=None) %}
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
{% block model_menu_bar %} {% block model_menu_bar %}
<ul class="nav nav-tabs actions-nav"> <ul class="nav nav-tabs actions-nav">
<li class="active"> <li class="active">
<a href="javascript:void(0)">{{ _gettext('List') }} ({{ count }})</a> <a href="javascript:void(0)">{{ _gettext('List') }}{% if count %} ({{ count }}){% endif %}</a>
</li> </li>
{% if admin_view.can_create %} {% if admin_view.can_create %}
<li> <li>
...@@ -146,7 +146,13 @@ ...@@ -146,7 +146,13 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
{% block list_pager %}
{% if num_pages is not none %}
{{ lib.pager(page, num_pages, pager_url) }} {{ lib.pager(page, num_pages, pager_url) }}
{% else %}
{{ lib.simple_pager(page, data|length == page_size, pager_url) }}
{% endif %}
{% endblock %}
{% endblock %} {% endblock %}
{{ actionlib.form(actions, get_url('.action_view')) }} {{ actionlib.form(actions, get_url('.action_view')) }}
......
...@@ -948,3 +948,20 @@ def test_form_args_embeddeddoc(): ...@@ -948,3 +948,20 @@ def test_form_args_embeddeddoc():
eq_(form.timestamp.label.text, 'Last Updated Time') eq_(form.timestamp.label.text, 'Last Updated Time')
# This is the failure # This is the failure
eq_(form.info.label.text, 'Information') eq_(form.info.label.text, 'Information')
def test_simple_list_pager():
app, db, admin = setup()
Model1, _ = create_models(db)
class TestModelView(CustomModelView):
simple_list_pager = True
def get_count_query(self):
assert False
view = TestModelView(Model1)
admin.add_view(view)
count, data = view.get_list(0, None, None, None, None)
ok_(count is None)
...@@ -12,6 +12,7 @@ from . import setup ...@@ -12,6 +12,7 @@ from . import setup
from datetime import datetime, time, date from datetime import datetime, time, date
class CustomModelView(ModelView): class CustomModelView(ModelView):
def __init__(self, model, session, def __init__(self, model, session,
name=None, category=None, endpoint=None, url=None, name=None, category=None, endpoint=None, url=None,
...@@ -1680,3 +1681,21 @@ def test_safe_redirect(): ...@@ -1680,3 +1681,21 @@ def test_safe_redirect():
assert_true(rv.location.startswith('http://localhost/admin/model1/edit/')) assert_true(rv.location.startswith('http://localhost/admin/model1/edit/'))
assert_true('url=%2Fadmin%2Fmodel1%2F' in rv.location) assert_true('url=%2Fadmin%2Fmodel1%2F' in rv.location)
assert_true('id=2' in rv.location) assert_true('id=2' in rv.location)
def test_simple_list_pager():
app, db, admin = setup()
Model1, _ = create_models(db)
db.create_all()
class TestModelView(CustomModelView):
simple_list_pager = True
def get_count_query(self):
assert False
view = TestModelView(Model1, db.session)
admin.add_view(view)
count, data = view.get_list(0, None, None, None, None)
assert_true(count is None)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment