Merge pull request #444 from dod-ccpo/paginate-audit-log
Paginate audit log
This commit is contained in:
commit
e6ca9d3b7e
@ -8,8 +8,9 @@ class AuditEventQuery(Query):
|
||||
model = AuditEvent
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return db.session.query(cls.model).order_by(cls.model.time_created.desc()).all()
|
||||
def get_all(cls, pagination_opts):
|
||||
query = db.session.query(cls.model).order_by(cls.model.time_created.desc())
|
||||
return cls.paginate(query, pagination_opts)
|
||||
|
||||
|
||||
class AuditLog(object):
|
||||
@ -28,11 +29,11 @@ class AuditLog(object):
|
||||
return cls._log(resource=resource, action=action)
|
||||
|
||||
@classmethod
|
||||
def get_all_events(cls, user):
|
||||
def get_all_events(cls, user, pagination_opts=None):
|
||||
Authorization.check_atat_permission(
|
||||
user, Permissions.VIEW_AUDIT_LOG, "view audit log"
|
||||
)
|
||||
return AuditEventQuery.get_all()
|
||||
return AuditEventQuery.get_all(pagination_opts)
|
||||
|
||||
@classmethod
|
||||
def _resource_type(cls, resource):
|
||||
|
@ -5,6 +5,39 @@ from atst.domain.exceptions import NotFoundError
|
||||
from atst.database import db
|
||||
|
||||
|
||||
class Paginator(object):
|
||||
"""
|
||||
Uses the Flask-SQLAlchemy extension's pagination method to paginate
|
||||
a query set.
|
||||
|
||||
Also acts as a proxy object so that the results of the query set can be iterated
|
||||
over without needing to call `.items`.
|
||||
"""
|
||||
|
||||
def __init__(self, query_set):
|
||||
self.query_set = query_set
|
||||
|
||||
@classmethod
|
||||
def paginate(cls, query, pagination_opts=None):
|
||||
if pagination_opts is not None:
|
||||
return cls(
|
||||
query.paginate(
|
||||
page=pagination_opts["page"], per_page=pagination_opts["per_page"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
return query.all()
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.query_set, name)
|
||||
|
||||
def __iter__(self):
|
||||
return self.items.__iter__()
|
||||
|
||||
def __len__(self):
|
||||
return self.items.__len__()
|
||||
|
||||
|
||||
class Query(object):
|
||||
|
||||
model = None
|
||||
@ -35,3 +68,7 @@ class Query(object):
|
||||
db.session.add(resource)
|
||||
db.session.commit()
|
||||
return resource
|
||||
|
||||
@classmethod
|
||||
def paginate(cls, query, pagination_opts):
|
||||
return Paginator.paginate(query, pagination_opts)
|
||||
|
@ -76,6 +76,16 @@ def dateFromString(value, formatter="%m/%Y"):
|
||||
return datetime.datetime.strptime(value, formatter)
|
||||
|
||||
|
||||
def pageWindow(pagination, size=2):
|
||||
page = pagination.page
|
||||
num_pages = pagination.pages
|
||||
|
||||
over = max(0, page + size - num_pages)
|
||||
under = min(0, page - size - 1)
|
||||
|
||||
return (max(1, (page - size) - over), min(num_pages, (page + size) - under))
|
||||
|
||||
|
||||
def register_filters(app):
|
||||
app.jinja_env.filters["iconSvg"] = iconSvg
|
||||
app.jinja_env.filters["dollars"] = dollars
|
||||
@ -87,3 +97,4 @@ def register_filters(app):
|
||||
app.jinja_env.filters["renderList"] = renderList
|
||||
app.jinja_env.filters["formattedDate"] = formattedDate
|
||||
app.jinja_env.filters["dateFromString"] = dateFromString
|
||||
app.jinja_env.filters["pageWindow"] = pageWindow
|
||||
|
@ -121,9 +121,17 @@ def logout():
|
||||
return redirect(url_for(".root"))
|
||||
|
||||
|
||||
def get_pagination_opts(request, default_page=1, default_per_page=100):
|
||||
return {
|
||||
"page": int(request.args.get("page", default_page)),
|
||||
"per_page": int(request.args.get("perPage", default_per_page)),
|
||||
}
|
||||
|
||||
|
||||
@bp.route("/activity-history")
|
||||
def activity_history():
|
||||
audit_events = AuditLog.get_all_events(g.current_user)
|
||||
pagination_opts = get_pagination_opts(request)
|
||||
audit_events = AuditLog.get_all_events(g.current_user, pagination_opts)
|
||||
return render_template("audit_log.html", audit_events=audit_events)
|
||||
|
||||
|
||||
|
@ -20,3 +20,15 @@
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin: .5em;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "components/pagination.html" import Pagination %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
@ -40,5 +41,5 @@
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
{{ Pagination(audit_events, 'atst.activity_history') }}
|
||||
{% endblock %}
|
||||
|
48
templates/components/pagination.html
Normal file
48
templates/components/pagination.html
Normal file
@ -0,0 +1,48 @@
|
||||
{% macro Page(pagination, route, i, label=None, disabled=False) -%}
|
||||
{% set label = label or i %}
|
||||
|
||||
{% set button_class = "page usa-button " %}
|
||||
|
||||
{% if disabled %}
|
||||
{% set button_class = button_class + "usa-button-disabled" %}
|
||||
{% elif i == pagination.page %}
|
||||
{% set button_class = button_class + "usa-button-primary" %}
|
||||
{% else %}
|
||||
{% set button_class = button_class + "usa-button-secondary" %}
|
||||
{% endif %}
|
||||
|
||||
<a id="{{ label }}" type="button" class="{{ button_class }}" href="{{ url_for(route, page=i, perPage=pagination.per_page) if not disabled else 'null' }}">{{ label }}</a>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro Pagination(pagination, route) -%}
|
||||
<div class="pagination">
|
||||
|
||||
{% if pagination.page == 1 %}
|
||||
{% set max_page = [pagination.pages, 5] | min %}
|
||||
{{ Page(pagination, route, 1, label="first", disabled=True) }}
|
||||
{{ Page(pagination, route, pagination.page - 1, label="prev", disabled=True) }}
|
||||
{% for i in range(1, max_page + 1) %}
|
||||
{{ Page(pagination, route, i) }}
|
||||
{% endfor %}
|
||||
{{ Page(pagination, route, pagination.page + 1, label="next") }}
|
||||
{{ Page(pagination, route, pagination.pages, label="last") }}
|
||||
{% elif pagination.page == pagination.pages %}
|
||||
{{ Page(pagination, route, 1, label="first") }}
|
||||
{{ Page(pagination, route, pagination.page - 1, label="prev") }}
|
||||
{% for i in range(pagination.pages - 4, pagination.pages + 1) %}
|
||||
{{ Page(pagination, route, i) }}
|
||||
{% endfor %}
|
||||
{{ Page(pagination, route, pagination.page + 1, label="next", disabled=True) }}
|
||||
{{ Page(pagination, route, pagination.pages, label="last", disabled=True) }}
|
||||
{% else %}
|
||||
{% set window = pagination | pageWindow %}
|
||||
{{ Page(pagination, route, 1, label="first") }}
|
||||
{{ Page(pagination, route, pagination.page - 1, label="prev") }}
|
||||
{% for i in range(window.0, window.1 + 1) %}
|
||||
{{ Page(pagination, route, i) }}
|
||||
{% endfor %}
|
||||
{{ Page(pagination, route, pagination.page + 1, label="next") }}
|
||||
{{ Page(pagination, route, pagination.pages, label="last") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- endmacro %}
|
@ -22,3 +22,12 @@ def test_non_admin_cannot_view_audit_log(developer):
|
||||
|
||||
def test_ccpo_can_iview_audit_log(ccpo):
|
||||
AuditLog.get_all_events(ccpo)
|
||||
|
||||
|
||||
def test_paginate_audit_log(ccpo):
|
||||
user = UserFactory.create()
|
||||
for _ in range(100):
|
||||
AuditLog.log_system_event(user, action="create")
|
||||
|
||||
events = AuditLog.get_all_events(ccpo, pagination_opts={"per_page": 25, "page": 2})
|
||||
assert len(events) == 25
|
||||
|
Loading…
x
Reference in New Issue
Block a user