{"id":16418488,"url":"https://github.com/nitely/django-infinite-scroll-pagination","last_synced_at":"2025-04-12T16:38:34.657Z","repository":{"id":15920099,"uuid":"18661851","full_name":"nitely/django-infinite-scroll-pagination","owner":"nitely","description":":cyclone: Pagination based on the seek method / keyset paging / offset-less pagination","archived":false,"fork":false,"pushed_at":"2025-02-14T12:38:53.000Z","size":78,"stargazers_count":123,"open_issues_count":3,"forks_count":13,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-03T22:06:44.115Z","etag":null,"topics":["django","hacktoberfest","infinite-scroll","keyset","keyset-paging","python","scrolling"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nitely.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":"nitely_","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"custom":null}},"created_at":"2014-04-11T04:08:11.000Z","updated_at":"2025-02-24T08:13:15.000Z","dependencies_parsed_at":"2024-03-27T01:24:34.550Z","dependency_job_id":"106e82e2-98da-4bc8-b11c-797c7844d82c","html_url":"https://github.com/nitely/django-infinite-scroll-pagination","commit_stats":{"total_commits":52,"total_committers":3,"mean_commits":"17.333333333333332","dds":0.3653846153846154,"last_synced_commit":"9df31eebe7c7f496e1e6353addc2909401b2b7c7"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nitely%2Fdjango-infinite-scroll-pagination","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nitely%2Fdjango-infinite-scroll-pagination/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nitely%2Fdjango-infinite-scroll-pagination/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nitely%2Fdjango-infinite-scroll-pagination/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nitely","download_url":"https://codeload.github.com/nitely/django-infinite-scroll-pagination/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248597824,"owners_count":21130953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","hacktoberfest","infinite-scroll","keyset","keyset-paging","python","scrolling"],"created_at":"2024-10-11T07:14:16.189Z","updated_at":"2025-04-12T16:38:34.636Z","avatar_url":"https://github.com/nitely.png","language":"Python","funding_links":["https://ko-fi.com/nitely_"],"categories":[],"sub_categories":[],"readme":"# infinite-scroll-pagination\n\n[![Build Status](https://img.shields.io/travis/nitely/django-infinite-scroll-pagination/master.svg?style=flat-square)](https://travis-ci.org/nitely/django-infinite-scroll-pagination)\n[![Coverage Status](https://img.shields.io/coveralls/nitely/django-infinite-scroll-pagination/master.svg?style=flat-square)](https://coveralls.io/r/nitely/django-infinite-scroll-pagination)\n[![pypi](https://img.shields.io/pypi/v/django-infinite-scroll-pagination.svg?style=flat-square)](https://pypi.python.org/pypi/django-infinite-scroll-pagination)\n[![licence](https://img.shields.io/pypi/l/django-infinite-scroll-pagination.svg?style=flat-square)](https://raw.githubusercontent.com/nitely/django-infinite-scroll-pagination/master/LICENSE)\n\ninfinite-scroll-pagination is a Django lib that implements\n[the seek method](http://use-the-index-luke.com/sql/partial-results/fetch-next-page)\n(AKA Keyset Paging or Cursor Pagination) for scalable pagination.\n\n\u003e Note despite its name, this library can be used as a regular paginator,\n  a better name would have been ``seek-paginator``, ``keyset-paginator``,\n  ``cursor-paginator`` or ``offset-less-paginator`` but it's too late for that now, haha :D\n\n## How it works\n\nKeyset driven paging relies on remembering the top and bottom keys of\nthe last displayed page, and requesting the next or previous set of rows,\nbased on the top/last keyset\n\nThis approach has two main advantages over the *OFFSET/LIMIT* approach:\n\n* is correct: unlike the *offset/limit* based approach it correctly handles\n  new entries and deleted entries. Last row of Page 4 does not show up as first\n  row of Page 5 just because row 23 on Page 2 was deleted in the meantime.\n  Nor do rows mysteriously vanish between pages. These anomalies are common\n  with the *offset/limit* based approach, but the *keyset* based solution does\n  a much better job at avoiding them.\n* is fast: all operations can be solved with a fast row positioning followed\n  by a range scan in the desired direction.\n\nFor a full explanation go to\n[the seek method](http://use-the-index-luke.com/sql/partial-results/fetch-next-page)\n\n## Requirements\n\ninfinite-scroll-pagination requires the following software to be installed:\n\n* Python +3.9\n* Django +4.2 LTS\n\n## Install\n\n```\npip install django-infinite-scroll-pagination\n```\n\n## Usage\n\nThis example paginates by a `created_at` date field:\n\n```python\n# views.py\n\nimport json\n\nfrom django.http import Http404, HttpResponse\n\nfrom infinite_scroll_pagination import paginator\nfrom infinite_scroll_pagination import serializers\n\nfrom .models import Article\n\n\ndef pagination_ajax(request):\n    if not request.is_ajax():\n        return Http404()\n\n    try:\n        value, pk = serializers.page_key(request.GET.get('p', ''))\n    except serializers.InvalidPage:\n        return Http404()\n\n    try:\n        page = paginator.paginate(\n            query_set=Article.objects.all(),\n            lookup_field='-created_at',\n            value=value,\n            pk=pk,\n            per_page=20,\n            move_to=paginator.NEXT_PAGE)\n    except paginator.EmptyPage:\n        data = {'error': \"this page is empty\"}\n    else:\n        data = {\n            'articles': [{'title': article.title} for article in page],\n            'has_next': page.has_next(),\n            'has_prev': page.has_previous(),\n            'next_objects_left': page.next_objects_left(limit=100),\n            'prev_objects_left': page.prev_objects_left(limit=100),\n            'next_pages_left': page.next_pages_left(limit=100),\n            'prev_pages_left': page.prev_pages_left(limit=100),\n            'next_page': serializers.to_page_key(**page.next_page()),\n            'prev_page': serializers.to_page_key(**page.prev_page())}\n\n    return HttpResponse(json.dumps(data), content_type=\"application/json\")\n```\n\n## Filter/sort by `pk`, `ìd`, or some `unique=True` field:\n\n```python\npage = paginator.paginate(queryset, lookup_field='pk', value=pk, per_page=20)\n```\n\n## Filter/sort by multiple fields:\n\n```python\npage = paginator.paginate(\n    queryset,\n    lookup_field=('-is_pinned', '-created_at', '-pk'),\n    value=(is_pinned, created_at, pk),\n    per_page=20)\n```\n\n\u003e Make sure the last field is `unique=True`, or `pk`\n\n## Items order\n\nDESC order:\n\n```python\npage = paginator.paginate(\n    # ...,\n    lookup_field='-created_at')\n```\n\nASC order:\n\n```python\npage = paginator.paginate(\n    # ...,\n    lookup_field='created_at')\n```\n\n## Fetch next or prev page\n\nPrev page:\n\n```python\npage = paginator.paginate(\n    # ...,\n    move_to=paginator.PREV_PAGE)\n```\n\nNext page:\n\n```python\npage = paginator.paginate(\n    # ...,\n    move_to=paginator.NEXT_PAGE)\n```\n\n## Serializers\n\nSince paginating by a datetime and a pk is so common,\nthere is a serializers that will convert both values to ``timestamp-pk``,\nfor example: ``1552349160.099628-5``, this can be later be used\nas a query string ``https://.../articles/?p=1552349160.099628-5``.\nThere is no need to do the conversion client side, the server can send\nthe next/previous page keyset serialized, as shown in the \"Usage\" section.\n\nSerialize:\n\n```python\nnext_page = serializers.to_page_key(**page.next_page())\nprev_page = serializers.to_page_key(**page.prev_page())\n```\n\nDeserialize:\n\n```python\nvalue, pk = serializers.page_key(request.GET.get('p', ''))\n```\n\n## Performance\n\nThe model should have an index that covers the paginate query.\nThe previous example's model would look like this:\n\n```python\nclass Article(models.Model):\n    title = models.CharField(max_length=255)\n    created_at = models.DateTimeField(default=timezone.now)\n\n    class Meta:\n        indexes = [\n            models.Index(fields=['created_at', 'id']),\n            models.Index(fields=['-created_at', '-id'])]\n```\n\n\u003e Note: an index is require for both directions,\n  since the query has a `LIMIT`.\n  See [indexes-ordering](https://www.postgresql.org/docs/9.3/indexes-ordering.html)\n\nHowever, this library does not implements the fast \"row values\"\nvariant of [the seek method](https://use-the-index-luke.com/sql/partial-results/fetch-next-page).\nWhat this means is the index is only\nused on the first field. If the first field is a boolean,\nthen it won't be used. So, it's pointless to index anything other than the first field.\nSee [PR #8](https://github.com/nitely/django-infinite-scroll-pagination/pull/8)\nif you are interested in benchmarks numbers, and please let me know\nif there is a way to implement the \"row values\" variant without using raw SQL.\n\nPass a limit to the following methods,\nor use them in places where there won't be\nmany records, otherwise they get expensive fast:\n\n* ``next_objects_left``\n* ``prev_objects_left``\n* ``next_pages_left``\n* ``prev_pages_left``\n\n## Contributing\n\nFeel free to check out the source code and submit pull requests.\n\nPlease, report any bug or propose new features in the\n[issues tracker](https://github.com/nitely/django-infinite-scroll-pagination/issues)\n\n## Copyright / License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnitely%2Fdjango-infinite-scroll-pagination","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnitely%2Fdjango-infinite-scroll-pagination","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnitely%2Fdjango-infinite-scroll-pagination/lists"}