Conversation
auvipy
left a comment
There was a problem hiding this comment.
I think this deserve a test case,, but as it is adding/exposing new API surface to the framework, we might need to pass the design decision step first
|
OK I saw the comment now #6185 (comment) it might be possible in class level but not globally at the moment. |
|
@auvipy I added tests for system checks, but didn't managed to find and add tests for settings with API requests like in |
|
we got a PR related to this area #8993. would be helpful if you review and share your views on the PR. |
|
@auvipy @christophehenry @TheSuperiorStanislav If you don't mind, I can take this up and implement the changes requested |
Co-authored-by: Roman Gorbil <roman.gorbil@saritasa.com>
456a1fe to
b51141c
Compare
|
@auvipy @christophehenry Could you please take a look? Sorry for taking that long, btw 🥲 |
| def page_size(self) -> int: | ||
| """Get default page size. | ||
|
|
||
| Defaults to `None`, meaning pagination is disabled. | ||
|
|
||
| """ |
There was a problem hiding this comment.
There shoud probably be a comment explaining why this i a property to avoid removing it in a future refactor?
|
Looks good to me. Thank you. |
|
@auvipy Could you take a look, please? This pr has been here for a while) |
| limit_query_description = _('Number of results to return per page.') | ||
| offset_query_param = 'offset' | ||
| offset_query_description = _('The initial index from which to return the results.') | ||
| max_limit = None |
There was a problem hiding this comment.
Can we avoid changing the style here?
max_limit = api_settings.MAX_PAGE_SIZEPerfer minimal impact footprints for PRs.
There was a problem hiding this comment.
It's related to #8993 and #9107 (comment). Without it we can't properly test it, since this setting will be set during init.
There was a problem hiding this comment.
Hrm... not sure? If you'd like to take that approach then I'd probably suggest handling #8993 separately, as a precursor to this.
Unclear to me why that'd be different to attributes we use in other cases.
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
|
2000 lines and 80 chars per line. |
There was a problem hiding this comment.
Pull request overview
Adds a global MAX_PAGE_SIZE REST framework setting to cap client-requested pagination sizes (avoiding unbounded page sizes without requiring custom pagination subclasses), and integrates it into built-in pagination classes, with docs and tests updates.
Changes:
- Add
MAX_PAGE_SIZEtoREST_FRAMEWORKdefault settings and extend the pagination system check to account for it. - Read
MAX_PAGE_SIZEfromapi_settingsin built-in paginators (PageNumber/LimitOffset/Cursor) via new properties. - Add/extend tests and documentation to cover the new setting and warning behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
rest_framework/settings.py |
Introduces MAX_PAGE_SIZE default setting. |
rest_framework/pagination.py |
Wires MAX_PAGE_SIZE into pagination implementations via settings-backed properties. |
rest_framework/checks.py |
Updates the pagination system check to warn when MAX_PAGE_SIZE is set without a default pagination class. |
tests/test_settings.py |
Extends system-check integration test to include MAX_PAGE_SIZE. |
tests/test_pagination.py |
Adds integration tests validating settings-based max page size behavior. |
docs/api-guide/pagination.md |
Documents global configuration including MAX_PAGE_SIZE. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def paginate_queryset(self, queryset, request, view=None): | ||
| self.request = request | ||
| self.page_size = self.get_page_size(request) |
There was a problem hiding this comment.
CursorPagination.page_size is now a read-only @Property, but paginate_queryset() assigns to self.page_size. That assignment will raise AttributeError at runtime. Consider either keeping page_size as a regular attribute, or adding a setter/backing attribute so paginate_queryset can cache the computed per-request page size safely.
| invalid_page_message = _('Invalid page.') | ||
|
|
||
| @property | ||
| def page_size(self) -> int: |
There was a problem hiding this comment.
The return type annotation here should allow None. api_settings.PAGE_SIZE defaults to None (pagination disabled), so annotating this as int is inaccurate and can confuse type checkers; consider using Optional[int]/int | None (or dropping the annotation to match the rest of the module).
| return api_settings.PAGE_SIZE | ||
|
|
||
| @property | ||
| def max_page_size(self) -> int: |
There was a problem hiding this comment.
Same type issue as page_size: api_settings.MAX_PAGE_SIZE defaults to None, so this property's return type should be Optional[int]/int | None rather than int (or omit the annotation for consistency).
| Set to an integer to limit the maximum page size the client may request. | ||
| Only relevant if 'page_size_query_param' has also been set. | ||
| Defaults to `None`, meaning page size is unlimited. | ||
| It's recommended that you would set a limit to avoid api abuse. |
There was a problem hiding this comment.
Docstring grammar/casing: “It's recommended that you would…” is ungrammatical, and “api” should be “API”. Consider rephrasing to “It's recommended that you set a limit to avoid API abuse.”
| It's recommended that you would set a limit to avoid api abuse. | |
| It's recommended that you set a limit to avoid API abuse. |
| Set to an integer to limit the maximum page size the client may request. | ||
| Only relevant if 'page_size_query_param' has also been set. | ||
| Defaults to `None`, meaning page size is unlimited. | ||
| It's recommended that you would set a limit to avoid api abuse. |
There was a problem hiding this comment.
LimitOffsetPagination uses limit_query_param, not page_size_query_param, so this docstring is misleading. Also the same grammar/casing issue applies (“API abuse”). Update the text to refer to limit_query_param (and/or max_limit) and fix the wording.
| Set to an integer to limit the maximum page size the client may request. | |
| Only relevant if 'page_size_query_param' has also been set. | |
| Defaults to `None`, meaning page size is unlimited. | |
| It's recommended that you would set a limit to avoid api abuse. | |
| Set to an integer to limit the maximum number of results the client may | |
| request via the ``limit_query_param``. | |
| Defaults to ``None``, meaning the limit is unrestricted. | |
| It is recommended that you set a limit to avoid API abuse. |
| Note that you need to set both the pagination class, and the page size and limit that should be used. | ||
| `DEFAULT_PAGINATION_CLASS`, `PAGE_SIZE`, `MAX_PAGE_SIZE` are `None` by default. |
There was a problem hiding this comment.
This note is a bit too strong/inaccurate: you don’t always need to set both PAGE_SIZE and MAX_PAGE_SIZE for pagination to work (e.g., LimitOffsetPagination can rely on the client-provided limit, and MAX_PAGE_SIZE only matters when a client-controllable size param is enabled). Consider rewording to clarify which settings are required vs optional depending on the pagination class.
| @override_settings( | ||
| REST_FRAMEWORK={ | ||
| "MAX_PAGE_SIZE": 20, | ||
| "PAGE_SIZE": 5, | ||
| } | ||
| ) |
There was a problem hiding this comment.
This test block switches to double quotes for strings (e.g., REST_FRAMEWORK keys and response.data indexing) while the surrounding tests in this module predominantly use single quotes. Consider using the existing quote style here for consistency and to reduce diff noise.
| return api_settings.PAGE_SIZE | ||
|
|
||
| @property | ||
| def max_page_size(self) -> int: |
There was a problem hiding this comment.
These settings-backed properties can return None (MAX_PAGE_SIZE defaults to None), but the return annotation is int. Consider changing to Optional[int]/int | None (or removing the annotation) to avoid misleading type information.
| template = 'rest_framework/pagination/numbers.html' | ||
|
|
||
| @property | ||
| def max_limit(self) -> int: |
There was a problem hiding this comment.
Same typing concern as elsewhere: api_settings.MAX_PAGE_SIZE (and PAGE_SIZE) default to None, so annotating these as int is inaccurate. Adjust the annotations to allow None or omit them for consistency with the rest of the module.
| It's recommended that you would set a limit to avoid api abuse. | ||
|
|
||
| """ | ||
| return api_settings.MAX_PAGE_SIZE |
There was a problem hiding this comment.
MAX_PAGE_SIZE is now wired into LimitOffsetPagination via max_limit (and also into CursorPagination/PageNumberPagination). There are new integration tests for PageNumberPagination settings, but no corresponding tests that MAX_PAGE_SIZE actually caps limit for LimitOffsetPagination (and/or caps cursor page size when enabled). Adding a couple of targeted tests would help prevent regressions across pagination styles.
Description
Add a setting for max page size for global pagination. Our team discovered that by default, page size is unlimited which could lead to API abuse and the only way to fix it -> it's to subclass pagination class(which is a working solution, but not convenient). After investigating issues I found related issue and comment.