From fe76f20f0c4cdd880ca82aa868bb76d43500c237 Mon Sep 17 00:00:00 2001
From: str4d <str4d@mail.i2p>
Date: Fri, 14 Dec 2012 06:26:21 +0000
Subject: [PATCH] Implemented pagination for meetings and blog entries

---
 i2p2www/__init__.py               | 41 +++++++++++++++++++++++++------
 i2p2www/helpers.py                | 32 ++++++++++++++++++++++++
 i2p2www/pages/blog/index.html     |  2 ++
 i2p2www/pages/global/macros       | 26 ++++++++++++++++++++
 i2p2www/pages/meetings/index.html |  2 ++
 5 files changed, 96 insertions(+), 7 deletions(-)
 create mode 100644 i2p2www/helpers.py

diff --git a/i2p2www/__init__.py b/i2p2www/__init__.py
index 74f924fbd..c8d98b11d 100644
--- a/i2p2www/__init__.py
+++ b/i2p2www/__init__.py
@@ -14,6 +14,7 @@ try:
 except ImportError:
     import simplejson as json
 
+from helpers import Pagination
 
 TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'pages')
 STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static')
@@ -21,6 +22,9 @@ STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static')
 BLOG_DIR = os.path.join(os.path.dirname(__file__), 'blog')
 MEETINGS_DIR = os.path.join(os.path.dirname(__file__), 'meetings')
 
+BLOG_ENTRIES_PER_PAGE = 20
+MEETINGS_PER_PAGE = 20
+
 MIRRORS_FILE = os.path.join(TEMPLATE_DIR, 'downloads/mirrors')
 
 app = application = Flask('i2p2www', template_folder=TEMPLATE_DIR, static_url_path='/_static', static_folder=STATIC_DIR)
@@ -141,7 +145,15 @@ def utility_processor():
         except KeyError:
             # The I2P site has no known clearnet address, so use an inproxy
             return value + '.to'
-    return dict(i2pconv=convert_url_to_clearnet)
+
+    # Convert a paginated URL to that of another page
+    def url_for_other_page(page):
+        args = request.view_args.copy()
+        args['page'] = page
+        return url_for(request.endpoint, **args)
+
+    return dict(i2pconv=convert_url_to_clearnet,
+                url_for_other_page=url_for_other_page)
 
 
 ################
@@ -156,6 +168,15 @@ def server_error(error):
     return render_template('global/error_500.html'), 500
 
 
+########################
+# General helper methods
+
+def get_for_page(items, page, per_page):
+    from_item = (page-1)*per_page
+    to_item = page*per_page
+    return items[from_item:to_item]
+
+
 #######################
 # General page handlers
 
@@ -267,9 +288,12 @@ def render_meeting_rst(id):
 @app.route('/<string:lang>/meetings/', defaults={'page': 1})
 @app.route('/<string:lang>/meetings/page/<int:page>')
 def meetings_index(page):
-    meetings = get_meetings()
-
-    return render_template('meetings/index.html', meetings=meetings)
+    all_meetings = get_meetings()
+    meetings = get_for_page(all_meetings, page, MEETINGS_PER_PAGE)
+    if not meetings and page != 1:
+        abort(404)
+    pagination = Pagination(page, MEETINGS_PER_PAGE, len(all_meetings))
+    return render_template('meetings/index.html', pagination=pagination, meetings=meetings)
 
 # Renderer for specific meetings
 @app.route('/<string:lang>/meetings/<int:id>')
@@ -476,9 +500,12 @@ def render_blog_entry(slug):
 @app.route('/<string:lang>/blog/', defaults={'page': 1})
 @app.route('/<string:lang>/blog/page/<int:page>')
 def blog_index(page):
-    entries = get_blog_entries()
-
-    return render_template('blog/index.html', entries=entries)
+    all_entries = get_blog_entries()
+    entries = get_for_page(all_entries, page, BLOG_ENTRIES_PER_PAGE)
+    if not entries and page != 1:
+        abort(404)
+    pagination = Pagination(page, BLOG_ENTRIES_PER_PAGE, len(all_entries))
+    return render_template('blog/index.html', pagination=pagination, entries=entries)
 
 @app.route('/<string:lang>/blog/entry/<path:slug>')
 def blog_entry(slug):
diff --git a/i2p2www/helpers.py b/i2p2www/helpers.py
new file mode 100644
index 000000000..bbd8a6563
--- /dev/null
+++ b/i2p2www/helpers.py
@@ -0,0 +1,32 @@
+from math import ceil
+
+class Pagination(object):
+    def __init__(self, page, per_page, total_count):
+        self.page = page
+        self.per_page = per_page
+        self.total_count = total_count
+
+    @property
+    def pages(self):
+        return int(ceil(self.total_count / float(self.per_page)))
+
+    @property
+    def has_prev(self):
+        return self.page > 1
+
+    @property
+    def has_next(self):
+        return self.page < self.pages
+
+    def iter_pages(self, left_edge=2, left_current=2,
+                   right_current=5, right_edge=2):
+        last = 0
+        for num in xrange(1, self.pages + 1):
+            if num <= left_edge or \
+               (num > self.page - left_current - 1 and \
+                num < self.page + right_current) or \
+               num > self.pages - right_edge:
+                if last + 1 != num:
+                    yield None
+                yield num
+                last = num
diff --git a/i2p2www/pages/blog/index.html b/i2p2www/pages/blog/index.html
index 2b8d7d056..06e997521 100644
--- a/i2p2www/pages/blog/index.html
+++ b/i2p2www/pages/blog/index.html
@@ -10,4 +10,6 @@
   <li>{{ entry[1] }} - <a href="{{ url_for('blog_entry', slug=entry[0]) }}">{{ entry[2] }}</a></li>
 {%- endfor %}
 </ul>
+{%- from "global/macros" import render_pagination with context -%}
+{{ render_pagination(pagination) | safe }}
 {% endblock %}
diff --git a/i2p2www/pages/global/macros b/i2p2www/pages/global/macros
index 8fd460a68..a1470155a 100644
--- a/i2p2www/pages/global/macros
+++ b/i2p2www/pages/global/macros
@@ -3,6 +3,7 @@
 {%- else -%}{{ url_for('site_show', lang=g.lang) }}
 {%- endif -%}
 {%- endmacro -%}
+
 {%- macro change_lang(lang) -%}
 {%- if request.endpoint == 'site_show' -%}{{ url_for('site_show', lang=lang, page=page) }}
 {%- elif request.endpoint == 'blog_entry' -%}{{ url_for('blog_entry', lang=lang, slug=slug) }}
@@ -12,8 +13,33 @@
 {%- else -%}{{ url_for('site_show', lang=lang) }}
 {%- endif -%}
 {%- endmacro -%}
+
 {%- macro ver(string=None) -%}
 {%- if string -%}{{ string % '0.9.3' }}
 {%- else -%}{{ '0.9.3' }}
 {%- endif -%}
 {%- endmacro -%}
+
+{%- macro render_pagination(pagination) %}
+  <div class="pagination">
+  {%- if pagination.has_prev %}
+    <a href="{{ url_for_other_page(pagination.page - 1)
+      }}">&laquo; Previous</a>
+  {%- endif %}
+  {%- for page in pagination.iter_pages() %}
+    {%- if page %}
+      {%- if page != pagination.page %}
+    <a href="{{ url_for_other_page(page) }}">{{ page }}</a>
+      {%- else %}
+    <strong>{{ page }}</strong>
+      {%- endif %}
+    {%- else %}
+    <span class="ellipsis">…</span>
+    {%- endif %}
+  {%- endfor %}
+  {%- if pagination.has_next %}
+    <a href="{{ url_for_other_page(pagination.page + 1)
+      }}">Next &raquo;</a>
+  {%- endif %}
+  </div>
+{%- endmacro -%}
diff --git a/i2p2www/pages/meetings/index.html b/i2p2www/pages/meetings/index.html
index 0046b71bb..8ca967923 100644
--- a/i2p2www/pages/meetings/index.html
+++ b/i2p2www/pages/meetings/index.html
@@ -15,4 +15,6 @@
 <li><a href="{{ meeting_url(meeting['id']) }}">Meeting {{ meeting['id'] }}</a>{% if meeting['date'] %} - {{ meeting['date'].strftime("%B %d, %Y") }}{% endif %}</li>
 {%- endfor %}
 </ul>
+{%- from "global/macros" import render_pagination with context -%}
+{{ render_pagination(pagination) | safe }}
 {% endblock %}
-- 
GitLab