Lazy load history view with pagination. refs #149
This commit is contained in:
parent
fefbfcd503
commit
e3ae3a1ccb
|
@ -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
|
||||||
|
|
|
@ -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>")
|
||||||
|
|
|
@ -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) }}"><</a>
|
||||||
|
{% else %}
|
||||||
|
<
|
||||||
|
{% endif %}
|
||||||
|
{{ pagination.page }}
|
||||||
|
{% if pagination.more %}
|
||||||
|
<a href="{{ url_for('.history', name=name, page=pagination.page + 1) }}">></a>
|
||||||
|
{% else %}
|
||||||
|
>
|
||||||
|
{% 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>
|
||||||
|
|
Loading…
Reference in a new issue