Short answer
No. Django REST Framework's CursorPagination cannot support arbitrary page jumps without reintroducing the exact performance penalty it was designed to eliminate. The class is intentionally sequential-only; there is no documented strategy that preserves both O(1) cursor seeks and random access.
Why the limitation exists
A cursor is not an index—it is an opaque, base64-encoded position (a field value plus a unique tiebreaker). When DRF receives a cursor, it filters the queryset with __lt or __gt against that position. The database never computes OFFSET, so deep pagination remains fast and stable under concurrent writes.
To honor a request like ?page=50, the server would have to materialize the cursor for the 50th page. That requires walking or skipping 49 * page_size rows—effectively an OFFSET query. Once you pay that cost, you have lost the performance benefit. Worse, an OFFSET-derived cursor is a point-in-time snapshot; if rows are inserted or deleted between requests, the "page 50" bookmark may no longer point to the same logical segment.
What the code confirms
In DRF's pagination.py, CursorPagination lacks a page property and a page-number parameter. Its get_page_number raises NotImplementedError. This is a deliberate design choice, not an oversight.
Practical paths forward
- Use
PageNumberPagination for jump-to-page UIs. Accept the OFFSET cost when users need deep links or a page selector.
- Keep
CursorPagination for feeds and infinite scroll. If users need to "jump," expose a search or filter endpoint that returns a cursor for the matching subset, then let them paginate sequentially from there.
- Hybrid approach with a one-time cost. You can subclass
CursorPagination to accept a page number, but the implementation must run an OFFSET query once to derive the cursor for that page. Subsequent next/previous links are fast again, but the first random jump pays the full penalty.
Consistency caveats
Cursor atomicity depends on ordering by a stable, indexed column (for example, created_at plus id as a tiebreaker) and on a transaction isolation level of REPEATABLE READ or higher. If you build a custom random-access layer, you must replicate DRF's tiebreaker logic exactly; otherwise cursors will mismatch. Also, cursors are opaque and version-dependent—long-lived URLs may break after DRF upgrades or schema migrations.
Verification
Inspect the installed DRF version's rest_framework/pagination.py to confirm the missing get_page_number implementation. Run EXPLAIN ANALYZE on a deep OFFSET query versus a cursor query to quantify the gap on your dataset. Insert rows during pagination to observe that cursors remain stable while offset pagination drifts.