feat(perf): borner les vues de liste, et choisir le motif une bonne fois
MNT-14. Chaque vue de liste faisait .all() sur sa table. L'audit evaluait l'impact a nul -- justement, a l'echelle d'une association etudiante -- et recommandait de choisir le motif maintenant plutot que de le retro-adapter plus tard. C'est ce que ceci est. Applique a list_users, list_evaluations et team_matches.list_matches. Pour cette derniere, la pagination borne aussi la boucle sur les participants, qui est le N+1 que le constat designait comme le premier a se degrader. Trois decisions, parce que ce sont celles qui se prennent deux fois differemment sinon. error_out=False : les numeros de page arrivent par l'URL, donc ?page=999 est une chose qu'on tape ou qu'un signet perime contient. Le defaut de Flask-SQLAlchemy y repond par un 404, ce qui est deroutant pour quelqu'un qui est simplement alle une page trop loin. Un plafond sur per_page : c'est aussi un parametre d'URL, et sans plafond ?per_page=100000 redonne a la main exactement la requete non bornee que la pagination existe pour empecher. page_url est un global Jinja plutot qu'une valeur que chaque vue passe. Ce qui se rate avec des liens de pagination, c'est le reste de la chaine de requete : la liste d'evaluations porte sort et order, celle des matchs d'equipe porte team_id, et un lien qui les perd reinitialise silencieusement la vue que la personne regardait. Les deux tests qui l'epinglent tombent si page_url cesse de les recopier -- verifie par mutation. Les tris sont completes par une cle unique : une requete paginee sans ORDER BY stable peut montrer la meme ligne deux fois et jamais une autre. Au passage, huit entrees fuzzy corrigees dans les catalogues, dont deux laissees par le commit SEC-16 : une entree fuzzy est ignoree a l'execution, donc ces messages retombaient en anglais. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
{#
|
||||
Pagination controls (MNT-14).
|
||||
|
||||
Import and call:
|
||||
{% import 'layouts/_pagination.html' as pager %}
|
||||
{{ pager.controls(pagination) }}
|
||||
|
||||
`page_url` is a Jinja global registered in app.py; it rebuilds the current
|
||||
URL at another page number, keeping the rest of the query string. That is
|
||||
the part that gets forgotten — dropping `sort` or `team_id` from a
|
||||
pagination link silently resets the view someone was looking at.
|
||||
|
||||
Plain <a> links only: no inline handler, nothing for CSP to refuse
|
||||
(tests/test_csp.py).
|
||||
#}
|
||||
|
||||
{% macro controls(pagination) %}
|
||||
{% if pagination.pages > 1 %}
|
||||
<nav class="pagination" aria-label="{{ _('Pagination') }}">
|
||||
{% if pagination.has_prev %}
|
||||
<a class="btn btn-secondary btn-sm" href="{{ page_url(pagination.prev_num) }}"
|
||||
rel="prev">« {{ _('Previous') }}</a>
|
||||
{% else %}
|
||||
<span class="btn btn-secondary btn-sm is-disabled" aria-disabled="true">« {{ _('Previous') }}</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="pagination-status">
|
||||
{{ _('Page %(page)s of %(pages)s', page=pagination.page, pages=pagination.pages) }}
|
||||
·
|
||||
{{ _('%(total)s in total', total=pagination.total) }}
|
||||
</span>
|
||||
|
||||
{% if pagination.has_next %}
|
||||
<a class="btn btn-secondary btn-sm" href="{{ page_url(pagination.next_num) }}"
|
||||
rel="next">{{ _('Next') }} »</a>
|
||||
{% else %}
|
||||
<span class="btn btn-secondary btn-sm is-disabled" aria-disabled="true">{{ _('Next') }} »</span>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
Reference in New Issue
Block a user