-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The main goal is to apply the same pagination method we just introduced for courses to a new entity and generalize the code around to avoid duplication. - Move dashboard pagination to its own module - Handle the limit request parameter directly in get_page - Replace query parameters in the original URL instead of constructing it from scratch - Use sqlalchemy.Select instead of sqlalchemy.Query. The latter is deprecated and we should move all API to the new SQLAlchemy 2.0 And finally: - Paginated API endpoint for assignments This is a barebones endpoint, it doesn't yet accept any query parameters for for example filter by course.
- Loading branch information
Showing
12 changed files
with
285 additions
and
166 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import json | ||
from typing import TypeVar | ||
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse | ||
|
||
from marshmallow import ValidationError, fields, post_load | ||
from sqlalchemy import Select | ||
|
||
from lms.js_config_types import Pagination | ||
from lms.models import Assignment, Course | ||
from lms.validation._base import PyramidRequestSchema | ||
|
||
T = TypeVar("T", Course, Assignment) | ||
"""Types for which support pagination.""" | ||
|
||
|
||
MAX_ITEMS_PER_PAGE = 100 | ||
"""Maximum number of items to return in paginated endpoints""" | ||
|
||
|
||
def _get_cursor_value(items: list[T], cursor_columns: list) -> str: | ||
last_element = items[-1] | ||
# Get the relevant values from the last element on the page | ||
values = [getattr(last_element, column.key) for column in cursor_columns] | ||
return json.dumps(values) | ||
|
||
|
||
def _get_next_url(current_url, cursor_value) -> str: | ||
"""Add or replace the cursor value on top of any other query parameters present in current_url.""" | ||
parsed_url = urlparse(current_url) | ||
query_params = parse_qs(parsed_url.query) | ||
|
||
query_params["cursor"] = [cursor_value] | ||
|
||
new_query_string = urlencode(query_params, doseq=True) | ||
|
||
return urlunparse(parsed_url._replace(query=new_query_string)) | ||
|
||
|
||
def get_page( | ||
request, items_query: Select[tuple[T]], cursor_columns: list | ||
) -> tuple[list[T], Pagination]: | ||
"""Return the first page and pagination metadata from a query.""" | ||
if cursor_values := request.parsed_params.get("cursor"): | ||
# If we have a cursor only fetch the elements that follow | ||
items_query = items_query.where(tuple(cursor_columns) > tuple(cursor_values)) # type: ignore | ||
|
||
limit = min(MAX_ITEMS_PER_PAGE, request.parsed_params["limit"]) | ||
# Over fetch one element to check if need to calculate the next cursor | ||
items = request.db.scalars(items_query.limit(limit + 1)).all() | ||
if not items or len(items) <= limit: | ||
# No elements or no next page, no pagination.next | ||
return items, Pagination(next=None) | ||
items = items[0:limit] | ||
|
||
cursor_value = _get_cursor_value(items, cursor_columns) | ||
return items, Pagination(next=_get_next_url(request.url, cursor_value)) | ||
|
||
|
||
class PaginationParametersMixin(PyramidRequestSchema): | ||
location = "query" | ||
|
||
limit = fields.Integer(required=False, load_default=MAX_ITEMS_PER_PAGE) | ||
"""Maximum number of items to return.""" | ||
|
||
cursor = fields.Str() | ||
"""Position to return elements from.""" | ||
|
||
@post_load | ||
def decode_cursor(self, in_data, **_kwargs): | ||
cursor = in_data.get("cursor") | ||
if not cursor: | ||
return in_data | ||
|
||
try: | ||
in_data["cursor"] = json.loads(cursor) | ||
except ValueError as exc: | ||
raise ValidationError("Invalid value for pagination cursor.") from exc | ||
|
||
return in_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.