List Pagination, Search and Sorting
Every resource list in the web console, e.g. the Guardrail, Secret or Prompt list, shows one
page of resources at a time, with previous and next page buttons, a page number box, and a
search, and its column headers sort it. Its Django list api, at
react-integration/api/listview/<owned|shared|all>/, does the paging, the searching and the
sorting, so a search covers all of the resources that the user may list, and a sort orders all
of them, not only those of the page shown.
The api’s optional query parameters are page, from 1; page_size, up to 100, and 25 by
default; search, the text that a resource’s name or description contains, ignoring case; and
ordering, the column to sort by, such as name, or -name to sort it in descending
order. Without an ordering, the most recently updated resources come first. Its response
describes the page that it returns, alongside its objects:
{
"objects": [{"name": "my_guardrail", "...": "..."}],
"pagination": {
"page": 2,
"pageSize": 25,
"numPages": 3,
"count": 61,
"search": "guard",
"ordering": "-name",
"sortFields": ["category", "createdAt", "description", "name", "stage", "updatedAt"]
}
}
A page past the last is the last, and an invalid page or page size is the default. Every list
api implements this with paginate_listview().
Each list api names the columns that it can sort by, in camelCase as its objects name them,
and the model field that sorts each one, e.g. "apiKey": "api_key__name" for a Provider’s API
key. Every list can sort by name, description, createdAt and updatedAt
(DEFAULT_SORT_FIELDS), and most add the columns of their
own resource, such as a Guardrail’s stage and category. An unknown ordering is ignored,
and sortFields lists the columns that the api can sort by, so that the React list makes only
those column headers clickable. Each click on a header cycles its column through ascending,
descending and the default order, and returns the list to its first page. Resources whose column
has the same value are ordered by their primary key, so that each appears on exactly one page.
Pagination and search for the list apis of the React list views in the Smarter web console.
Every resource list, e.g. the Guardrail or Secret list, is a TabbedListView of
@smarter/common, which asks its Django list api for one page of the resources at a time,
optionally narrowed by a search, and sorted by one of its columns. paginate_listview()
reads those query parameters, filters, sorts and paginates the queryset, and describes the page,
so that every list api behaves alike.
The list is sorted by the database, not in the browser, because a page holds only some of the resources: sorting the rows of the page shown would leave the rest of the resources in their default order, on the other pages.
SORT_FIELDS = {**DEFAULT_SORT_FIELDS, "stage": "stage"}
page, pagination = paginate_listview(request, qs.order_by("-updated_at"), sort_fields=SORT_FIELDS)
return JsonResponse(
{
"objects": GuardrailSerializer(page, many=True, context={"request": request}).data,
"pagination": pagination,
}
)
- smarter.lib.django.pagination.DEFAULT_PAGE_SIZE = 25
The resources of a page, unless the list asks for another number.
- smarter.lib.django.pagination.DEFAULT_SEARCH_FIELDS = ('name', 'description')
The fields that a search matches, of the MetaDataModel that every Smarter resource inherits.
('name', 'description')
- smarter.lib.django.pagination.DEFAULT_SORT_FIELDS: Mapping[str, str] = {'createdAt': 'created_at', 'description': 'description', 'name': 'name', 'updatedAt': 'updated_at'}
The columns that a list may be sorted by, of the MetaDataModel that every Smarter resource inherits.
Each key is a column’s name in the list api’s response, in camelCase, as the
orderingquery parameter names it. Each value is the field that sorts it, in the queryset’s lookup syntax, such as"api_key__name"for a related resource’s name.{ 'createdAt': 'created_at', 'description': 'description', 'name': 'name', 'updatedAt': 'updated_at', }
- class smarter.lib.django.pagination.ListViewPagination(**data)[source]
Bases:
BaseModelThe description of one page of a list api’s resources, as
paginate_listview()returns it.Its JSON is camelCase, e.g.
pageSize.- model_config = {'alias_generator': <function to_camel>, 'extra': 'forbid', 'serialize_by_alias': True, 'validate_by_alias': True, 'validate_by_name': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- smarter.lib.django.pagination.MAX_PAGE_SIZE = 100
The most resources that a page may have.
- smarter.lib.django.pagination.MAX_SEARCH_LENGTH = 255
The longest search, in characters.
A longer one is truncated.
- smarter.lib.django.pagination.has_field(model, lookup)[source]
Whether
modelhas the field thatlookupnames, following relations, e.g."api_key__name".
- smarter.lib.django.pagination.paginate_listview(request, queryset, search_fields=('name', 'description'), sort_fields={'createdAt': 'created_at', 'description': 'description', 'name': 'name', 'updatedAt': 'updated_at'})[source]
One page of a list api’s resources, which match its search, and a description of the page.
The request’s query parameters are all optional:
page: from 1. A page past the last is the last, and an invalid one is the first.page_size: up toMAX_PAGE_SIZE;DEFAULT_PAGE_SIZEif invalid.search: text that one ofsearch_fieldscontains, ignoring case.ordering: the column ofsort_fieldsto sort by, e.g.name, or-nameto sort it in descending order. Without one, the resources keep the queryset’s order. Seesort_queryset().
- Parameters:
request (
HttpRequest) – The list api’s request.queryset (
QuerySet) – The resources that the user may list, in their default order.search_fields (
Sequence[str]) – The names of the fields that a search matches.sort_fields (
Mapping[str,str]) – The columns that the list may be sorted by, and the fields that sort them. SeeDEFAULT_SORT_FIELDS.
- Return type:
- Returns:
The page, and its description for the list api’s response, a dumped
ListViewPagination:{"page": 1, "pageSize": 25, "numPages": 3, "count": 61, "search": "", "ordering": "-name", "sortFields": ["createdAt", "description", "name", "updatedAt"]}.orderingis empty when the default order applies, andsortFieldsare the columns that the list may be sorted by, so that the list shows only those columns as sortable.
- smarter.lib.django.pagination.positive_int(value, default, maximum=None)[source]
An integer query parameter, from 1 up to
maximum, if any, or elsedefault.
- smarter.lib.django.pagination.search_queryset(queryset, search, search_fields=('name', 'description'))[source]
The resources of
querysetthat one ofsearch_fieldscontainssearchin, ignoring case.Fields that the queryset’s model does not have are ignored. An empty search, or one with no field to match, returns the queryset unchanged.
- smarter.lib.django.pagination.sort_queryset(queryset, ordering, sort_fields={'createdAt': 'created_at', 'description': 'description', 'name': 'name', 'updatedAt': 'updated_at'})[source]
The resources of
queryset, sorted by the column thatorderingnames.orderingis a column ofsort_fields, such as"name", or, to sort it in descending order, the column after a minus sign, such as"-name". Empty values sort after the others, in either direction. An empty or unknown ordering leaves the queryset’s own order.The resources’ primary key breaks ties, in the same direction, so that resources whose column has the same value, e.g. the same stage, are in the same order on every request. Otherwise the database could return them in a different order for each page, and a resource could appear on two pages, or on none.
- Parameters:
- Return type:
- Returns:
The sorted resources, and the ordering that sorted them:
ordering, or an empty string if the queryset kept its own order.