├── .gitignore ├── LICENSE ├── README.md ├── example ├── example.png ├── myapp.py ├── page-info.png ├── requirements.txt ├── sql.py └── templates │ ├── base.html │ └── index.html ├── python_paginate ├── __init__.py ├── css │ ├── __init__.py │ ├── basecss.py │ ├── bootstrap.py │ ├── foundation.py │ ├── ink.py │ ├── metro.py │ ├── semantic.py │ └── uikit.py └── web │ ├── __init__.py │ ├── base_paginate.py │ ├── flask_paginate.py │ ├── sanic_paginate.py │ └── tornado_paginate.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | example/test.db 3 | example/app.cfg 4 | venv 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | 19 | # Installer logs 20 | pip-log.txt 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | 26 | #Translations 27 | *.mo 28 | 29 | #Mr Developer 30 | .mr.developer.cfg 31 | /docs/_build 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 by Lix Xu. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-paginate 2 | 3 | Pagination support for python web frameworks (study from will_paginate). 4 | It requires Python2.6+ as string.format syntax. 5 | 6 | Supported CSS: `bootstrap2&3&4`, `foundation`, `semanticui`, `ink`, `uikit` and `metro4` 7 | 8 | Supported web frameworks: Flask, Tornado and Sanic 9 | 10 | ```text 11 | Notice: 12 | Only SemanticUI is tested as I'm using it, please submit PR if has bugs. 13 | ``` 14 | 15 | ## Installation 16 | 17 | `pip install python-paginate` 18 | 19 | If you want to show page info like this: 20 | ![page-info](example/page-info.png "page-info") 21 | above the pagination links, 22 | please add below lines to your css file: 23 | 24 | ```css 25 | 26 | .pagination-page-info { 27 | padding: .6em; 28 | padding-left: 0; 29 | width: 40em; 30 | margin: .5em; 31 | margin-left: 0; 32 | font-size: 12px; 33 | } 34 | .pagination-page-info b { 35 | color: black; 36 | background: #6aa6ed; 37 | padding-left: 2px; 38 | padding: .1em .25em; 39 | font-size: 150%; 40 | } 41 | ``` 42 | 43 | ## Usage 44 | 45 | see examples and source code for details. 46 | 47 | Run example: 48 | 49 | ```bash 50 | $cd example 51 | $virtualenv venv 52 | $. venv/bin/activate 53 | $pip install -r requirements.txt 54 | $python sql.py --help 55 | $python sql.py init_db 56 | $python sql.py fill_data --total=307 57 | $python myapp.py 58 | ``` 59 | 60 | Open to see the example page. 61 | 62 | ![example](example/example.png "example") 63 | -------------------------------------------------------------------------------- /example/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixxu/python-paginate/75bd0746d253070a89d08eb5e19f2cf46d93126a/example/example.png -------------------------------------------------------------------------------- /example/myapp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import sqlite3 6 | import tempfile 7 | 8 | from python_paginate.css.semantic import Semantic 9 | from python_paginate.web.sanic_paginate import Pagination 10 | from sanic import Sanic 11 | from sanic_jinja2 import SanicJinja2 12 | 13 | app = Sanic(__name__) 14 | 15 | # update pagination settings 16 | settings = dict( 17 | PREV_LABEL='', 18 | NEXT_LABEL='', 19 | PER_PAGE=10, # default is 10 20 | DB_PATH=os.path.join(tempfile.gettempdir(), "test.db"), 21 | ) 22 | app.config.update(settings) 23 | 24 | jinja = SanicJinja2(app, autoescape=True) 25 | 26 | # customize default pagination 27 | if "PREV_LABEL" in app.config: 28 | Semantic._prev_label = app.config.PREV_LABEL 29 | 30 | if "NEXT_LABEL" in app.config: 31 | Semantic._next_label = app.config.NEXT_LABEL 32 | 33 | Pagination._css = Semantic() # for cache 34 | # or 35 | # Pagination._css_framework = 'semantic' 36 | # like above line, but little different 37 | # if you want to get same result, need do below: 38 | # pass css_prev_label, css_next_label to Pagination for initialize 39 | 40 | Pagination._per_page = app.config.PER_PAGE 41 | 42 | 43 | @app.route("/") 44 | async def index(request): 45 | conn = sqlite3.connect(app.config.DB_PATH) 46 | conn.row_factory = sqlite3.Row 47 | cur = conn.cursor() 48 | cur.execute("select count(*) from users") 49 | total = cur.fetchone()[0] 50 | page, per_page, offset = Pagination.get_page_args(request) 51 | sql = "select name from users limit {}, {}".format(offset, per_page) 52 | cur.execute(sql) 53 | users = cur.fetchall() 54 | cur.close() 55 | conn.close() 56 | pagination = Pagination(request, total=total, record_name="users") 57 | return jinja.render("index.html", request, users=users, pagination=pagination) 58 | 59 | 60 | if __name__ == "__main__": 61 | app.run(host="127.0.0.1", port=8000, debug=True) 62 | -------------------------------------------------------------------------------- /example/page-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixxu/python-paginate/75bd0746d253070a89d08eb5e19f2cf46d93126a/example/page-info.png -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | sanic 2 | sanic-jinja2 3 | python-paginate 4 | click 5 | -------------------------------------------------------------------------------- /example/sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os.path 5 | import sqlite3 6 | import tempfile 7 | 8 | import click 9 | 10 | sql = """create table if not exists users( 11 | id integer primary key autoincrement, 12 | name varchar(30) 13 | ) 14 | """ 15 | 16 | DB_PATH = os.path.join(tempfile.gettempdir(), "test.db") 17 | 18 | 19 | @click.group() 20 | def cli(): 21 | pass 22 | 23 | 24 | @cli.command(short_help="initialize database and tables") 25 | def init_db(): 26 | conn = sqlite3.connect(DB_PATH) 27 | cur = conn.cursor() 28 | cur.execute(sql) 29 | conn.commit() 30 | conn.close() 31 | 32 | 33 | @cli.command(short_help="fill records to database") 34 | @click.option("--total", "-t", default=300, help="fill data for example") 35 | def fill_data(total): 36 | conn = sqlite3.connect(DB_PATH) 37 | cur = conn.cursor() 38 | for i in range(total): 39 | cur.execute("insert into users (name) values (?)", ["name" + str(i)]) 40 | 41 | conn.commit() 42 | conn.close() 43 | 44 | 45 | if __name__ == "__main__": 46 | cli() 47 | -------------------------------------------------------------------------------- /example/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | My pagination app example 11 | 12 | 13 | 14 | 130 | 153 | 154 | 155 | 156 | 157 | 162 | 163 | 164 | 165 | 168 | 169 | 170 | 171 |
172 |
173 | 174 |
175 | 181 |
182 |
183 |
184 | {% set messages = get_flashed_messages(with_categories=true) %} 185 | {% if messages %} 186 |
187 |
188 | 189 |
190 | {% if messages|length > 1 %} 191 |
    192 | {% for cat, msg in messages %}
  • {{ _(msg) }}
  • {% endfor %} 193 |
194 | {% else %}

{{ _(messages[0][1]) }}

195 | {% endif %} 196 |
197 |
198 |
199 | {% endif %} 200 |
{% block body %}{% endblock %}
201 |
202 |
203 | 204 | 208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /example/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Users{% endblock %} 4 | 5 | {% block body %} 6 | {% if users %} 7 | {{ pagination.info }} 8 |
9 | 10 | 11 | 12 | 13 | 14 | {% for user in users %} 15 | 16 | 17 | 18 | 19 | {% endfor %} 20 | 21 | {% if pagination.links %} 22 | 23 | 24 | 27 | 28 | 29 | {% endif %} 30 |
#Name
{{ loop.index + pagination.skip }}{{ user.name }}
25 | 26 |
31 |
32 | {% endif %} 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /python_paginate/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | pagation for python web frameworks 6 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 | 8 | Adds pagination support to python web frameworks. 9 | 10 | :copyright: (c) 2016 by Lix Xu. 11 | :license: BSD, see LICENSE for more details 12 | """ 13 | 14 | from __future__ import unicode_literals 15 | 16 | __version__ = "2024.05.15" 17 | -------------------------------------------------------------------------------- /python_paginate/css/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lixxu/python-paginate/75bd0746d253070a89d08eb5e19f2cf46d93126a/python_paginate/css/__init__.py -------------------------------------------------------------------------------- /python_paginate/css/basecss.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | 6 | INIT_KEYS = "size align extra head end normal actived gap gap_marker \ 7 | prev_label next_label prev_disabled next_disabled prev_normal next_normal\ 8 | ".split() 9 | 10 | 11 | class BaseCSS(object): 12 | _size = "" 13 | _align = "" 14 | _extra = "" 15 | _size_prefix = " " 16 | _align_prefix = " " 17 | _extra_prefix = " " 18 | _head = '
' 19 | _end = "
" 20 | _normal = '{label}' 21 | _actived = '{label}' 22 | _gap = '
{gap}
' 23 | _gap_marker = "..." 24 | _prev_label = "«" 25 | _next_label = "»" 26 | _prev_disabled = '
{label}
' 27 | _next_disabled = '
{label}
' 28 | _prev_normal = '{label}' 29 | _next_normal = '{label}' 30 | 31 | def __init__(self, **kwargs): 32 | [ 33 | setattr(self, k, kwargs.get("css_" + k, getattr(self, "_" + k))) 34 | for k in INIT_KEYS 35 | ] 36 | 37 | self.size = self.adjust_size(self.size) 38 | self.align = self.adjust_align(self.align) 39 | self.extra = self.ajust_extra(self.extra) 40 | self.gap = self.gap.format(gap=self.gap_marker) 41 | self.head = self.head.format( 42 | size=self.size, align=self.align, extra=self.extra 43 | ) 44 | 45 | def adjust_size(self, size=""): 46 | return self._size_prefix + size if size else "" 47 | 48 | def adjust_align(self, align=""): 49 | return self._align_prefix + align if align else "" 50 | 51 | def ajust_extra(self, extra=""): 52 | return self._extra_prefix + extra if extra else "" 53 | 54 | def get_normal(self, href="", label=""): 55 | return self.normal.format(href=href, label=label) 56 | 57 | def get_actived(self, href="", label=""): 58 | return self.actived.format(href=href, label=label) 59 | 60 | def get_side(self, href="#", disabled=False, side="prev"): 61 | if disabled: 62 | fmt = getattr(self, side + "_disabled") 63 | else: 64 | fmt = getattr(self, side + "_normal") 65 | 66 | return fmt.format(href=href, label=getattr(self, side + "_label")) 67 | 68 | def get_prev(self, href="", disabled=False): 69 | return self.get_side(href, disabled=disabled, side="prev") 70 | 71 | def get_next(self, href="", disabled=False): 72 | return self.get_side(href, disabled=disabled, side="next") 73 | -------------------------------------------------------------------------------- /python_paginate/css/bootstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | 6 | from python_paginate.css import basecss 7 | 8 | 9 | class BaseBootstrap(basecss.BaseCSS): 10 | _size_prefix = " pagination-" 11 | _align_prefix = " pagination-" 12 | _normal = '
  • {label}
  • ' 13 | _prev_normal = '
  • {label}
  • ' 14 | _next_normal = '
  • {label}
  • ' 15 | 16 | def __init__(self, **kwargs): 17 | super(BaseBootstrap, self).__init__(**kwargs) 18 | 19 | 20 | class Bootstrap2(BaseBootstrap): 21 | _head = '
    " 23 | _actived = '
  • {label}
  • ' 24 | _gap = '
  • {gap}
  • ' 25 | _prev_disabled = '
  • {label}
  • ' 26 | _next_disabled = '
  • {label}
  • ' 27 | 28 | def __init__(self, **kwargs): 29 | super(Bootstrap2, self).__init__(**kwargs) 30 | 31 | 32 | class Bootstrap3(BaseBootstrap): 33 | _head = '" 35 | _actived = '
  • {label} \ 36 | (current)
  • ' 37 | _gap = '
  • \ 38 |
  • ' 39 | _prev_disabled = '
  • \ 40 |
  • ' 41 | _next_disabled = '
  • \ 42 |
  • ' 43 | 44 | def __init__(self, **kwargs): 45 | super(Bootstrap3, self).__init__(**kwargs) 46 | 47 | 48 | class Bootstrap4(Bootstrap3): 49 | _align_prefix = " justify-content-" 50 | _normal = '
  • \ 51 | {label}
  • ' 52 | 53 | _actived = '
  • \ 54 | {label}
  • ' 55 | 56 | _gap = '
  • {gap}\ 57 |
  • ' 58 | 59 | _prev_disabled = '
  • \ 60 | {label}
  • ' 61 | 62 | _next_disabled = '
  • \ 63 | {label}
  • ' 64 | 65 | _prev_normal = '
  • \ 66 | {label}
  • ' 67 | 68 | _next_normal = '
  • \ 69 | {label}
  • ' 70 | 71 | def __init__(self, **kwargs): 72 | super(Bootstrap4, self).__init__(**kwargs) 73 | 74 | 75 | class Bootstrap5(Bootstrap4): 76 | pass 77 | -------------------------------------------------------------------------------- /python_paginate/css/foundation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | from python_paginate.css import basecss 6 | 7 | 8 | class Foundation(basecss.BaseCSS): 9 | _align_prefix = " text-" 10 | _head = '" 13 | 14 | _normal = '
  • {label}
  • ' 15 | _actived = '
  • \ 16 | Current {label}
  • ' 17 | 18 | _gap = ' ' 19 | 20 | _prev_disabled = '
  • {label} \ 21 | page
  • ' 22 | 23 | _next_disabled = '
  • {label} \ 24 | page
  • ' 25 | 26 | _prev_normal = '
  • \ 27 | {label} \ 28 | page
  • ' 29 | 30 | _next_normal = '
  • \ 31 | {label} \ 32 | page
  • ' 33 | 34 | def __init__(self, **kwargs): 35 | super(Foundation, self).__init__(**kwargs) 36 | -------------------------------------------------------------------------------- /python_paginate/css/ink.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | from python_paginate.css import basecss 6 | 7 | 8 | class Ink(basecss.BaseCSS): 9 | _head = '" 12 | 13 | _normal = '
  • {label}
  • ' 14 | _actived = '
  • {label}
  • ' 15 | _gap = '
  • {gap}
  • ' 16 | 17 | _prev_disabled = '
  • {label}
  • ' 18 | _next_disabled = '
  • {label}
  • ' 19 | 20 | _prev_normal = '
  • {label}
  • ' 21 | _next_normal = '
  • {label}
  • ' 22 | 23 | def __init__(self, **kwargs): 24 | super(Ink, self).__init__(**kwargs) 25 | -------------------------------------------------------------------------------- /python_paginate/css/metro.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | from python_paginate.css import basecss 6 | 7 | NORMAL = ( 8 | '
  • {label}
  • ' 9 | ) 10 | ACTIVED = '
  • {label}
  • ' 11 | PREV_DISABLED = '
  • {label}
  • ' 13 | NEXT_DISABLED = PREV_DISABLED 14 | NEXT_NORMAL = '
  • {label}
  • ' 16 | 17 | 18 | class Metro4(basecss.BaseCSS): 19 | _head = '" 21 | _normal = NORMAL 22 | _actived = ACTIVED 23 | _gap = '' 24 | _prev_label = '' 25 | _next_label = '' 26 | _prev_disabled = PREV_DISABLED 27 | _next_disabled = NEXT_DISABLED 28 | _prev_normal = NORMAL 29 | _next_normal = NEXT_NORMAL 30 | 31 | def __init__(self, **kwargs): 32 | super(Metro4, self).__init__(**kwargs) 33 | -------------------------------------------------------------------------------- /python_paginate/css/semantic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals 5 | from python_paginate.css import basecss 6 | 7 | 8 | class Semantic(basecss.BaseCSS): 9 | _head = '