Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search - Pagination #2683

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/dissemination/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class SearchForm(forms.Form):
(x, str(x)) for x in range(2016, 2024)
) # ((2016, "2016"), (2017, "2017"), ..., (2023, "2023"))

# Query params
entity_name = forms.CharField(required=False)
uei_or_ein = forms.CharField(required=False)
aln = forms.CharField(required=False)
Expand All @@ -14,3 +15,7 @@ class SearchForm(forms.Form):
cog_or_oversight = forms.CharField(required=False)
agency_name = forms.CharField(required=False)
audit_year = forms.MultipleChoiceField(choices=AY_choices, required=False)

# Display params
limit = forms.CharField(required=False)
page = forms.CharField(required=False)
2 changes: 1 addition & 1 deletion backend/dissemination/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ def search_general(
fiscal_year_match = Q(audit_year__in=audit_years)
query.add(fiscal_year_match, Q.AND)

results = General.objects.filter(query)
results = General.objects.filter(query).order_by("-fac_accepted_date")

return results
155 changes: 79 additions & 76 deletions backend/dissemination/templates/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ <h3>Filters</h3>
aria-controls="entity-name">Name (Entity, Auditee, or Auditor)</button>
</label>
<textarea class="usa-textarea" id="entity-name" name="entity_name">{{form.cleaned_data.entity_name}}</textarea>
{% comment %} Fiscal Year {% endcomment %}
{% comment %} Audit Year {% endcomment %}
<button type="button"
class="usa-accordion__button"
aria-expanded="true"
aria-controls="audit-year">Audit Year</button>
<div id="audit-year">
{% for value, text in form.audit_year.field.choices %}
<div class="usa-checkbox bg-base-lightest">
<div class="usa-checkbox bg-base-lighter">
<input class="usa-checkbox__input"
id="audit-year-{{ text }}"
name="audit_year"
Expand Down Expand Up @@ -121,14 +121,30 @@ <h3>Filters</h3>
<input class="usa-button" type="submit" value="Search" />
<button class="usa-button usa-button--unstyled">Reset Search</button>
</div>
{% comment %} Hidden page input for use when clicking pagination buttons {% endcomment %}
<input class="usa-input"
id="page"
name="page"
type="number"
value="{{ page }}"
hidden />
</form>
</div>
<div class="tablet:grid-col-8 audit-search-results">
<h2>Search single audit reports</h2>
<h2 class="font-sans-2xl">Search single audit reports</h2>
{% if results %}
<div class="usa-alert usa-alert--info">
<div class="usa-alert__body">
<h4 class="usa-alert__heading">Sorting</h4>
<p class="usa-alert__text">
Use the arrows at the top of each column to sort results. Sorting only applies to the results shown per page.
</p>
</div>
</div>
<table class="usa-table usa-table--striped">
<caption>
Results {{ results|length }}
Results: {{ results_count }}
<span class="margin-left-2 text-normal text-italic">showing {{ limit }} per page</span>
</caption>
<thead>
<tr>
Expand All @@ -137,16 +153,18 @@ <h2>Search single audit reports</h2>
<th data-sortable scope="col" role="columnheader">Acc Date</th>
<th data-sortable scope="col" role="columnheader">AY</th>
<th data-sortable scope="col" role="columnheader">Cog or Over</th>
<th data-sortable scope="col" role="columnheader">View</th>
<th data-sortable scope="col" role="columnheader">PDF</th>
<th scope="col" role="columnheader">View</th>
<th scope="col" role="columnheader">PDF</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr>
<th scope="row" role="rowheader">{{ result.auditee_name }}</th>
<td>{{ result.auditee_uei }}</td>
<td data-sort-value="1696528569380">{{ result.fac_accepted_date }}</td>
{% comment %} Sorts ascending/descending by the numeric date string (i.e. 20231231) {% endcomment %}
<td data-sort-value={{ result.fac_accepted_date|date:"Ymd" }}>{{ result.fac_accepted_date }}
</td>
<td>{{ result.audit_year }}</td>
<td>
{% if result.oversight_agency %}
Expand All @@ -168,11 +186,13 @@ <h2>Search single audit reports</h2>
</a>
</td>
<td>
<a href="{% url 'dissemination:PdfDownload' report_id=result.report_id %}" target="_blank">
<a class="usa-link display-flex flex-column flex-align-center"
href="{% url 'dissemination:PdfDownload' report_id=result.report_id %}"
target="_blank">
<svg class="usa-icon usa-icon--size-4"
aria-hidden="true"
focusable="false"
role="img">
aria-hidden="true"
focusable="false"
role="img">
{% uswds_sprite "file_download" %}
</svg>
</a>
Expand All @@ -183,70 +203,53 @@ <h2>Search single audit reports</h2>
</table>
<nav aria-label="Pagination" class="usa-pagination">
<ul class="usa-pagination__list">
<li class="usa-pagination__item usa-pagination__arrow">
<a href="javascript:void(0);"
class="usa-pagination__link usa-pagination__previous-page"
aria-label="Previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
{% uswds_sprite "navigate_before" %}
</svg>
<span class="usa-pagination__link-text">Previous</span></a>
</li>
<li class="usa-pagination__item usa-pagination__page-no">
<a href="javascript:void(0);"
class="usa-pagination__button"
aria-label="Page 1">1</a>
</li>
<li class="usa-pagination__item usa-pagination__overflow"
aria-label="ellipsis indicating non-visible pages">
<span>…</span>
</li>
<li class="usa-pagination__item usa-pagination__page-no">
<a href="javascript:void(0);"
class="usa-pagination__button"
aria-label="Page 9">9</a>
</li>
<li class="usa-pagination__item usa-pagination__page-no">
<a href="javascript:void(0);"
class="usa-pagination__button usa-current"
aria-label="Page 10"
aria-current="page">10</a>
</li>
<li class="usa-pagination__item usa-pagination__page-no">
<a href="javascript:void(0);"
class="usa-pagination__button"
aria-label="Page 11">11</a>
</li>
<li class="usa-pagination__item usa-pagination__overflow"
aria-label="ellipsis indicating non-visible pages">
<span>…</span>
</li>
<li class="usa-pagination__item usa-pagination__page-no">
<a href="javascript:void(0);"
class="usa-pagination__button"
aria-label="Last page, page 24">24</a>
</li>
<li class="usa-pagination__item usa-pagination__arrow">
<a href="javascript:void(0);"
class="usa-pagination__link usa-pagination__next-page"
aria-label="Next page"><span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
{% uswds_sprite "navigate_next" %}
</svg>
</a>
</li>
</ul>
</nav>
{% else %}
<div class="search-instructions">
<img src="{% static 'img/circle-arrow.svg' %}"
alt="an arrow points left, toward the search form" />
<p>
Enter your filters and select <em>Search</em> to begin
</p>
{% if results.has_previous %}
<li class="usa-pagination__item usa-pagination__arrow">
<a class="usa-pagination__link usa-pagination__previous-page"
aria-label="Previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
{% uswds_sprite "navigate_before" %}
</svg>
<span class="usa-pagination__link-text">Previous</span></a>
</li>
{% endif %}
{% for page_number in results.adjusted_elided_pages %}
{% if page_number == results.paginator.ELLIPSIS %}
<li class="usa-pagination__item usa-pagination__overflow"
aria-label="ellipsis indicating non-visible pages">
<span>…</span>
</li>
{% else %}
<li class="usa-pagination__item usa-pagination__page-no">
<a class="usa-pagination__button {% if results.number == page_number %}usa-current{% endif %}"
aria-label="Page {{ page_number }}">{{ page_number }}</a>
</li>
{% endif %}
{% endfor %}
{% if results.has_next %}
<li class="usa-pagination__item usa-pagination__arrow">
<a class="usa-pagination__link usa-pagination__next-page"
aria-label="Next page">
<span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
{% uswds_sprite "navigate_next" %}
</svg>
</a>
</li>
{% endif %}
</ul>
</nav>
{% else %}
<div class="search-instructions">
<img src="{% static 'img/circle-arrow.svg' %}"
alt="an arrow points left, toward the search form" />
<p>
Enter your filters and select <em>Search</em> to begin
</p>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
<script src="{% static 'compiled/js/search-results-pagination.js' %}"></script>
{% endblock content %}
30 changes: 28 additions & 2 deletions backend/dissemination/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.core.exceptions import PermissionDenied
from django.core.exceptions import BadRequest, PermissionDenied
from django.core.paginator import Paginator
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.views.generic import View
Expand Down Expand Up @@ -30,6 +31,7 @@ def get(self, request, *args, **kwargs):
def post(self, request, *args, **kwargs):
form = SearchForm(request.POST)
results = []
context = {}

if form.is_valid():
names = form.cleaned_data["entity_name"].splitlines()
Expand All @@ -42,6 +44,11 @@ def post(self, request, *args, **kwargs):
int(year) for year in form.cleaned_data["audit_year"]
] # Cast strings from HTML to int

# TODO: Add a limit choice field to the form
limit = form.cleaned_data["limit"] or 30
# Changed in the form via pagination links
page = form.cleaned_data["page"] or 1

results = search_general(
names,
uei_or_eins,
Expand All @@ -51,13 +58,32 @@ def post(self, request, *args, **kwargs):
agency_name,
audit_years,
)
results_count = results.count() # Total result count
paginator = Paginator(
results, per_page=limit
) # Paginator object handles results splicing, page count, and pagination buttons
results = paginator.get_page(page) # Results for a given page
results.adjusted_elided_pages = paginator.get_elided_page_range(
page, on_each_side=1
) # Pagination buttons, adjust ellipses around the current page

# Reformat these so the date-picker elements in HTML prepopulate
if form.cleaned_data["start_date"]:
form.cleaned_data["start_date"] = start_date.strftime("%Y-%m-%d")
if form.cleaned_data["end_date"]:
form.cleaned_data["end_date"] = end_date.strftime("%Y-%m-%d")
else:
raise BadRequest("Form data validation error.", form.errors)

context = context | {
"form": form,
"limit": limit,
"results": results,
"results_count": results_count,
"page": page,
}

return render(request, "search.html", {"form": form, "results": results})
return render(request, "search.html", context)


class AuditSummaryView(View):
Expand Down
38 changes: 38 additions & 0 deletions backend/static/js/search-results-pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
var FORM = document.forms[1];
const pagination_links = document.querySelectorAll('[aria-label^="Page"]');
const next_page_link = document.querySelectorAll('[aria-label="Next page"]');
const previous_page_link = document.querySelectorAll(
'[aria-label="Previous page"]'
);

function attachEventHandlers() {
// If any pagination links are clicked, set the form element and submit it for a reload
pagination_links.forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
FORM.elements['page'].value = link.textContent;
FORM.submit();
});
});
// If the next or previous page buttons are clicked, set the form element to be +/- 1 and submit for a reload
if (next_page_link[0]) {
next_page_link[0].addEventListener('click', (e) => {
e.preventDefault();
FORM.elements['page'].value = parseInt(FORM.elements['page'].value) + 1;
FORM.submit();
});
}
if (previous_page_link[0]) {
previous_page_link[0].addEventListener('click', (e) => {
e.preventDefault();
FORM.elements['page'].value = parseInt(FORM.elements['page'].value) - 1;
FORM.submit();
});
}
}

function init() {
attachEventHandlers();
}

window.onload = init;