Search code examples
javascriptpythonhtmlflaskjinja2

Pagination of pages


I have been looking for an answer for a long time how to implement pagination on the site, I use only Python and its frameworks. Is it even possible to do this? If so, how to use the jinja template engine to create a new page automatically with ten or more discussion elements. If there is no python solution, then I would really like to know how to implement it through js along with html in order to at least understand the mechanics.

html

            {% for discussion in discussions_json %}
            <a style="font-size: 19px; margin-right: auto; width: 88%; color:white; text-decoration:none; " href="/community/discussion/{{ discussion.id }}">{{ discussion.title }}</a> <span style="font-size: 25px; float: right; color: #0abab5;">{{ discussion.date.strftime("%H:%M | %d.%m.%Y") }}</span>
              <p style="font-size: 19px; margin-right: auto; width: 88%; color: red;">@{{ discussion.username }}</p>
              <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-chat-dots-fill" viewBox="0 0 16 16">
                <path d="M16 8c0 3.866-3.582 7-8 7a9 9 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7M5 8a1 1 0 1 0-2 0 1 1 0 0 0 2 0m4 0a1 1 0 1 0-2 0 1 1 0 0 0 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
              </svg>
              <span> {{ discussion.answer_count }}</span>
              <hr />
            {% endfor %}

python

@blueprint.route('/community')
def community_page():
    discussions_json = []
    discussions_list = Discussions.query.filter(Discussions.title.isnot(None)).order_by(Discussions.date.desc()).all()
    for discussion in discussions_list:
        discussions_json.append(
            {
                "id": discussion.id,
                "title": discussion.title,
                "text": discussion.text,
                "date": discussion.date,
                "username": User.query.filter_by(id=discussion.autor).first().username,
                "answer_count": discussion.answer_count()
            }
        )

    return render_template("community.html", discussions_json=discussions_json)


Solution

  • The following example shows you how you can implement pagination with the Flask-SQLAlchemy legacy API you are using.

    Instead of a request with a final all(), the command paginate(page=None, per_page=None, error_out=True, max_per_page=None) is used. It is then possible to add navigation for all available pages in the template. In the example this is achieved using a jinja macro. The database entries that were previously available directly are now accessible via the items attribute of the pagination object.

    from flask import (
        Flask, 
        render_template, 
        request
    )
    from flask_sqlalchemy import SQLAlchemy 
    from sqlalchemy import func
    from sqlalchemy.ext.associationproxy import association_proxy
    
    app = Flask(__name__)
    app.config.from_mapping(
        SQLALCHEMY_DATABASE_URI='sqlite:///example.db'
    )
    db = SQLAlchemy(app)
    
    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        username = db.Column(db.String, nullable=False, unique=True)
        # ...
        discussions = db.relationship('Discussion', backref='author')
    
    class Discussion(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        title = db.Column(db.String)
        text = db.Column(db.Text)
        date = db.Column(db.DateTime,
            nullable=False, 
            server_default=func.now())
        author_id = db.Column(db.Integer,
            db.ForeignKey('user.id'),
            nullable=False)
        # ...
        username = association_proxy('author', 'username')
    
    with app.app_context():
        db.drop_all()
        db.create_all()
    
        user = User(username='dummy')
        db.session.add(user)
    
        discussions = [
            Discussion(
                author=user, 
                title=f'Title-{i}', 
                text='Your text here!'
            )
            for i in range(1, 21)
        ]
        db.session.add_all(discussions)
        db.session.commit()
    
    @app.route('/community')
    def community_page():
        per_page = 5
        page = request.args.get('page', 1, type=int)
        discussions = Discussion.query\
            .filter(Discussion.title.isnot(None))\
            .order_by(Discussion.date.desc())\
            .paginate(page=page, per_page=per_page)
        return render_template('community.html', **locals())
    
    # ...
    
    {% macro render_pagination(pagination, endpoint) %}
      <div class=pagination>
      {%- for page in pagination.iter_pages() %}
        {% if page %}
          {% if page != pagination.page %}
            <a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
          {% else %}
            <strong>{{ page }}</strong>
          {% endif %}
        {% else %}
          <span class=ellipsis>…</span>
        {% endif %}
      {%- endfor %}
      </div>
    {% endmacro %}
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Community</title>
    </head>
    <body>
        {% for discussion in discussions.items -%}
            <a href="/community/discussion/{{ discussion.id }}">{{ discussion.title }}</a>
            <span>{{ discussion.date.strftime("%H:%M | %d.%m.%Y") }}</span>
            <p>@{{ discussion.username }}</p>
            <hr />
        {% endfor -%}
        {{ render_pagination(discussions, request.endpoint) }}
    </body>
    </html>
    

    If you are interested in how pagination can be implemented with the latest query variant, I recommend this article.