Lazy load history view with pagination. refs #149

This commit is contained in:
Chase Sterling 2016-07-07 02:49:52 -04:00
parent fefbfcd503
commit e3ae3a1ccb
3 changed files with 107 additions and 29 deletions

View file

@ -1,3 +1,5 @@
import collections
import itertools
import os import os
import posixpath import posixpath
import re import re
@ -98,37 +100,14 @@ class WikiPage(object):
cache.set(cache_key, info) cache.set(cache_key, info)
return info return info
def get_history(self, limit=100): @property
def history(self):
"""Get page history. """Get page history.
:param limit: Limit history size.
:return: list -- List of dicts :return: list -- List of dicts
""" """
if not len(self.wiki.repo.open_index()): return PageHistory(self, self._cache_key('history'))
# Index is empty, no commits
return []
versions = []
walker = self.wiki.repo.get_walker(paths=[self.filename], max_entries=limit)
for entry in walker:
change_type = None
for change in entry.changes():
if change.old.path == self.filename:
change_type = change.type
elif change.new.path == self.filename:
change_type = change.type
author_name, author_email = entry.commit.author.rstrip('>').split('<')
versions.append(dict(
author=author_name.strip(),
author_email=author_email,
time=entry.commit.author_time,
message=entry.commit.message,
sha=entry.commit.id,
type=change_type))
return versions
@property @property
def partials(self): def partials(self):
@ -319,3 +298,75 @@ class WikiPage(object):
# We'll get a KeyError if self.sha isn't in the repo, or if self.filename isn't in the tree of our commit # We'll get a KeyError if self.sha isn't in the repo, or if self.filename isn't in the tree of our commit
return False return False
return True return True
class PageHistory(collections.Sequence):
"""Acts like a list, but dynamically loads and caches history revisions as requested."""
def __init__(self, page, cache_key):
self.page = page
self.cache_key = cache_key
self._store = cache.get(cache_key) or []
if not self._store:
self._iter_rest = self._get_rest()
elif self._store[-1] == 'TAIL':
self._iter_rest = None
else:
self._iter_rest = self._get_rest(self._store[-1]['sha'])
def __iter__(self):
# Iterate over the revisions already cached
for r in self._store:
if r == 'TAIL':
return
yield r
# Iterate over the revisions yet to be discovered
if self._iter_rest:
try:
for r in self._iter_rest:
self._store.append(r)
yield r
self._store.append('TAIL')
finally:
# Make sure we cache the results whether or not the iteration was completed
cache.set(self.cache_key, self._store)
def _get_rest(self, start_sha=None):
if not len(self.page.wiki.repo.open_index()):
# Index is empty, no commits
return
walker = iter(self.page.wiki.repo.get_walker(paths=[self.page.filename], include=start_sha, follow=True))
if start_sha:
# If we are not starting from HEAD, we already have the start commit
print(next(walker))
filename = self.page.filename
for entry in walker:
change_type = None
for change in entry.changes():
if change.new.path == filename:
filename = change.old.path
change_type = change.type
break
author_name, author_email = entry.commit.author.rstrip('>').split('<')
r = dict(author=author_name.strip(),
author_email=author_email,
time=entry.commit.author_time,
message=entry.commit.message,
sha=entry.commit.id,
type=change_type)
yield r
def __getitem__(self, index):
if isinstance(index, slice):
return list(itertools.islice(self, index.start, index.stop, index.step))
else:
try:
return next(itertools.islice(self, index, index+1))
except StopIteration:
raise IndexError
def __len__(self):
if not self._store or self._store[-1] != 'TAIL':
# Force generation of all revisions
list(self)
return len(self._store) - 1 # Don't include the TAIL sentinel

View file

@ -64,11 +64,22 @@ def revert():
def history(name): def history(name):
if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous(): if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous():
return current_app.login_manager.unauthorized() return current_app.login_manager.unauthorized()
ITEMS_PER_PAGE = 15
page = int(request.args.get('page', 1))
start_index = (page - 1) * ITEMS_PER_PAGE
hist = g.current_wiki.get_page(name).get_history() hist = g.current_wiki.get_page(name).get_history()
for item in hist: # Grab one extra item to see if there is a next page
items = hist[start_index:start_index+ITEMS_PER_PAGE+1]
more = False
if len(items) > ITEMS_PER_PAGE:
more = True
items.pop()
if page > 1 and not items:
abort(404)
for item in items:
item['gravatar'] = gravatar_url(item['author_email']) item['gravatar'] = gravatar_url(item['author_email'])
return render_template('wiki/history.html', name=name, history=hist) return render_template('wiki/history.html', name=name, history=items,
pagination={'page': page, 'more': more})
@blueprint.route("/_edit/<path:name>") @blueprint.route("/_edit/<path:name>")

View file

@ -28,6 +28,22 @@
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
<!-- TODO: make this pagination look good -->
{% if pagination.page > 1 or pagination.more %}
<div>
{% if pagination.page > 1 %}
<a href="{{ url_for('.history', name=name, page=pagination.page - 1) }}">&lt;</a>
{% else %}
&lt;
{% endif %}
{{ pagination.page }}
{% if pagination.more %}
<a href="{{ url_for('.history', name=name, page=pagination.page + 1) }}">&gt;</a>
{% else %}
&gt;
{% endif %}
</div>
{% endif %}
<p> <p>
<a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a> <a class="btn btn-default btn-sm compare-revisions">Compare Revisions</a>
</p> </p>