diff --git a/realms/modules/wiki/models.py b/realms/modules/wiki/models.py index 3c3a160..7531d1d 100644 --- a/realms/modules/wiki/models.py +++ b/realms/modules/wiki/models.py @@ -1,3 +1,5 @@ +import collections +import itertools import os import posixpath import re @@ -98,37 +100,14 @@ class WikiPage(object): cache.set(cache_key, info) return info - def get_history(self, limit=100): + @property + def history(self): """Get page history. - :param limit: Limit history size. :return: list -- List of dicts """ - if not len(self.wiki.repo.open_index()): - # 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 + return PageHistory(self, self._cache_key('history')) @property 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 return False 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 diff --git a/realms/modules/wiki/views.py b/realms/modules/wiki/views.py index 40571b2..d850ff5 100644 --- a/realms/modules/wiki/views.py +++ b/realms/modules/wiki/views.py @@ -64,11 +64,22 @@ def revert(): def history(name): if current_app.config.get('PRIVATE_WIKI') and current_user.is_anonymous(): 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() - 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']) - 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/") diff --git a/realms/templates/wiki/history.html b/realms/templates/wiki/history.html index bdc0cc7..c5c253d 100644 --- a/realms/templates/wiki/history.html +++ b/realms/templates/wiki/history.html @@ -28,6 +28,22 @@ {% endfor %} + + {% if pagination.page > 1 or pagination.more %} +
+ {% if pagination.page > 1 %} + < + {% else %} + < + {% endif %} + {{ pagination.page }} + {% if pagination.more %} + > + {% else %} + > + {% endif %} +
+ {% endif %}

Compare Revisions