From e6bc4928c9e6fc7f1582702512c47de5b314374e Mon Sep 17 00:00:00 2001 From: Matthew Scragg Date: Mon, 20 Oct 2014 17:27:38 -0500 Subject: [PATCH] Fix #24 #21 WIP commit. Changed routes to POST/PUT/DELETE on page name endpoint to be more RESTful. Check wiki dir permissions Add comments Add dummy favicon, robots.txt, humans.txt Remove create.html (wasn't being used) Fix version command --- VERSION | 2 +- realms/__init__.py | 3 +- realms/cli.py | 4 +- realms/modules/wiki/__init__.py | 13 ++ realms/modules/wiki/models.py | 207 +++++++++++++++++++++++------- realms/modules/wiki/views.py | 152 ++++++++++++---------- realms/static/humans.txt | 0 realms/static/img/favicon.ico | Bin 0 -> 32038 bytes realms/static/js/editor.js | 24 +++- realms/static/js/main.js | 40 ++++++ realms/static/robots.txt | 0 realms/templates/wiki/create.html | 18 --- realms/templates/wiki/page.html | 2 +- 13 files changed, 321 insertions(+), 144 deletions(-) create mode 100644 realms/static/humans.txt create mode 100644 realms/static/img/favicon.ico create mode 100644 realms/static/js/main.js create mode 100644 realms/static/robots.txt delete mode 100644 realms/templates/wiki/create.html diff --git a/VERSION b/VERSION index 719cd12..771f209 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.19 \ No newline at end of file +0.3.20 \ No newline at end of file diff --git a/realms/__init__.py b/realms/__init__.py index 6875fae..a5c0481 100644 --- a/realms/__init__.py +++ b/realms/__init__.py @@ -210,7 +210,8 @@ assets.register('main.js', 'vendor/datatables/media/js/jquery.dataTables.js', 'vendor/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.js', 'js/hbs-helpers.js', - 'js/mdr.js') + 'js/mdr.js', + 'js/main.js') assets.register('main.css', 'vendor/bootswatch-dist/css/bootstrap.css', diff --git a/realms/cli.py b/realms/cli.py index a8a7e89..be4237a 100644 --- a/realms/cli.py +++ b/realms/cli.py @@ -330,8 +330,8 @@ def test(): def version(): """ Output version """ - with open('VERSION') as f: - return f.read().strip() + with open(os.path.join(config.APP_PATH, 'VERSION')) as f: + click.echo(f.read().strip()) if __name__ == '__main__': diff --git a/realms/modules/wiki/__init__.py b/realms/modules/wiki/__init__.py index e69de29..2c61d69 100644 --- a/realms/modules/wiki/__init__.py +++ b/realms/modules/wiki/__init__.py @@ -0,0 +1,13 @@ +import os +import sys +from realms import app +from realms.modules.wiki.models import Wiki + +# Init Wiki +Wiki(app.config['WIKI_PATH']) + +# Check paths +for mode in [os.W_OK, os.R_OK]: + for dir_ in [app.config['WIKI_PATH'], os.path.join(app.config['WIKI_PATH'], '.git')]: + if not os.access(dir_, mode): + sys.exit('Read and write access to WIKI_PATH is required (%s)' % dir_) diff --git a/realms/modules/wiki/models.py b/realms/modules/wiki/models.py index 6f87a20..5d6b3fa 100644 --- a/realms/modules/wiki/models.py +++ b/realms/modules/wiki/models.py @@ -14,15 +14,30 @@ from realms.lib.hook import HookMixin def cname_to_filename(cname): + """ Convert canonical name to filename + + :param cname: Canonical name + :return: str -- Filename + + """ return cname.lower() + ".md" def filename_to_cname(filename): - """ It's assumed filename is already cname format + """Convert filename to canonical name. + + .. note:: + + It's assumed filename is already canonical format + """ return os.path.splitext(filename)[0] +class PageNotFound(Exception): + pass + + class Wiki(HookMixin): path = None base_path = '/' @@ -48,56 +63,42 @@ class Wiki(HookMixin): return "Wiki: %s" % self.path def revert_page(self, name, commit_sha, message, username): + """Revert page to passed commit sha1 + + :param name: Name of page to revert. + :param commit_sha: Commit Sha1 to revert to. + :param message: Commit message. + :param username: + :return: Git commit sha1 + + """ page = self.get_page(name, commit_sha) if not page: - # Page not found - return None - commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')]) - message = commit_info['message'] + raise PageNotFound() + + if not message: + commit_info = gittle.utils.git.commit_info(self.gittle[commit_sha.encode('latin-1')]) + message = commit_info['message'] + return self.write_page(name, page['data'], message=message, username=username) def write_page(self, name, content, message=None, create=False, username=None, email=None): + """Write page to git repo - def escape_repl(m): - if m.group(1): - return "```" + escape(m.group(1)) + "```" - - def unescape_repl(m): - if m.group(1): - return "```" + unescape(m.group(1)) + "```" + :param name: Name of page. + :param content: Content of page. + :param message: Commit message. + :param create: Perform git add operation? + :param username: Commit Name. + :param email: Commit Email. + :return: Git commit sha1. + """ cname = to_canonical(name) - - # prevents p tag from being added, we remove this later - content = '
' + content + '
' - content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL) - - tree = lxml.html.fromstring(content) - - cleaner = Cleaner(remove_unknown_tags=False, - kill_tags=set(['style']), - safe_attrs_only=False) - tree = cleaner.clean_html(tree) - - content = lxml.html.tostring(tree, encoding='utf-8', method='html') - - # remove added div tags - content = content[5:-6] - - # FIXME this is for block quotes, doesn't work for double ">" - content = re.sub(r"(\n>)", "\n>", content) - content = re.sub(r"(^>)", ">", content) - - # Handlebars partial ">" - content = re.sub(r"\{\{>(.*?)\}\}", r'{{>\1}}', content) - - # Handlebars, allow {{}} inside HTML links - content = content.replace("%7B", "{") - content = content.replace("%7D", "}") - - content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL) - filename = cname_to_filename(cname) + + content = self.clean(content) + with open(self.path + "/" + filename, 'w') as f: f.write(content) @@ -122,7 +123,60 @@ class Wiki(HookMixin): return ret + def clean(self, content): + """Clean any HTML, this might not be necessary. + + :param content: Content of page. + :return: str + + """ + def escape_repl(m): + if m.group(1): + return "```" + escape(m.group(1)) + "```" + + def unescape_repl(m): + if m.group(1): + return "```" + unescape(m.group(1)) + "```" + + # prevents p tag from being added, we remove this later + content = '
' + content + '
' + content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL) + + tree = lxml.html.fromstring(content) + + cleaner = Cleaner(remove_unknown_tags=False, + kill_tags={'style'}, + safe_attrs_only=False) + tree = cleaner.clean_html(tree) + + content = lxml.html.tostring(tree, encoding='utf-8', method='html') + + # remove added div tags + content = content[5:-6] + + # FIXME this is for block quotes, doesn't work for double ">" + content = re.sub(r"(\n>)", "\n>", content) + content = re.sub(r"(^>)", ">", content) + + # Handlebars partial ">" + content = re.sub(r"\{\{>(.*?)\}\}", r'{{>\1}}', content) + + # Handlebars, allow {{}} inside HTML links + content = content.replace("%7B", "{") + content = content.replace("%7D", "}") + + content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL) + return content + def rename_page(self, old_name, new_name, user=None): + """Rename page. + + :param old_name: Page that will be renamed. + :param new_name: New name of page. + :param user: User object if any. + :return: str -- Commit sha1 + + """ old_filename, new_filename = map(cname_to_filename, [old_name, new_name]) if old_filename not in self.gittle.index: # old doesn't exist @@ -137,14 +191,38 @@ class Wiki(HookMixin): self.gittle.add(new_filename) self.gittle.rm(old_filename) - self.gittle.commit(name=getattr(user, 'username', self.default_committer_name), - email=getattr(user, 'email', self.default_committer_email), - message="Moved %s to %s" % (old_name, new_name), - files=[old_filename, new_filename]) + commit = self.gittle.commit(name=getattr(user, 'username', self.default_committer_name), + email=getattr(user, 'email', self.default_committer_email), + message="Moved %s to %s" % (old_name, new_name), + files=[old_filename, new_filename]) cache.delete_many(old_filename, new_filename) + return commit + + def delete_page(self, name, user=None): + """Delete page. + :param name: Page that will be deleted + :param user: User object if any + :return: str -- Commit sha1 + + """ + self.gittle.rm(name) + commit = self.gittle.commit(name=getattr(user, 'username', self.default_committer_name), + email=getattr(user, 'email', self.default_committer_email), + message="Deleted %s" % name, + files=[name]) + cache.delete_many(name) + return commit + def get_page(self, name, sha='HEAD'): + """Get page data, partials, commit info. + + :param name: Name of page. + :param sha: Commit sha. + :return: dict + + """ cached = cache.get(name) if cached: return cached @@ -172,22 +250,46 @@ class Wiki(HookMixin): return None def get_meta(self, content): + """Get metadata from page if any. + + :param content: Page content + :return: dict + + """ if not content.startswith("---"): return None + meta_end = re.search("\n(\.{3}|\-{3})", content) + if not meta_end: return None + try: return yaml.safe_load(content[0:meta_end.start()]) except Exception as e: return {'error': e.message} def compare(self, name, old_sha, new_sha): + """Compare two revisions of the same page. + + :param name: Name of page. + :param old_sha: Older sha. + :param new_sha: Newer sha. + :return: str - Raw markup with styles + + """ + + # TODO: This could be effectively done in the browser old = self.get_page(name, sha=old_sha) new = self.get_page(name, sha=new_sha) return ghdiff.diff(old['data'], new['data']) def get_index(self): + """Get repo index of head. + + :return: list -- List of dicts + + """ rv = [] index = self.repo.open_index() for name in index: @@ -201,8 +303,20 @@ class Wiki(HookMixin): return rv def get_history(self, name, limit=100): + """Get page history. + + :param name: Name of page. + :param limit: Limit history size. + :return: list -- List of dicts + + """ + if not len(self.repo.open_index()): + # Index is empty, no commits + return [] + file_path = cname_to_filename(name) versions = [] + walker = self.repo.get_walker(paths=[file_path], max_entries=limit) for entry in walker: change_type = None @@ -220,4 +334,3 @@ class Wiki(HookMixin): type=change_type)) return versions - diff --git a/realms/modules/wiki/views.py b/realms/modules/wiki/views.py index 503dd7b..84e0078 100644 --- a/realms/modules/wiki/views.py +++ b/realms/modules/wiki/views.py @@ -1,4 +1,4 @@ -from flask import g, render_template, request, redirect, Blueprint, flash, url_for, current_app +from flask import abort, g, render_template, request, redirect, Blueprint, flash, url_for, current_app from flask.ext.login import login_required from realms.lib.util import to_canonical, remove_ext from realms.modules.wiki.models import Wiki @@ -17,10 +17,11 @@ def commit(name, sha): cname = to_canonical(name) data = g.current_wiki.get_page(cname, sha=sha) - if data: - return render_template('wiki/page.html', name=name, page=data, commit=sha) - else: - return redirect(url_for('wiki.create', name=cname)) + + if not data: + abort(404) + + return render_template('wiki/page.html', name=name, page=data, commit=sha) @blueprint.route("/_compare//") @@ -35,15 +36,17 @@ def revert(): name = request.form.get('name') commit = request.form.get('commit') cname = to_canonical(name) + message = request.form.get('message', "Reverting %s" % cname) - if cname in app.config.WIKI_LOCKED_PAGES: - flash("Page is locked") - return redirect(url_for(app.config['ROOT_ENDPOINT'])) + if cname in app.config.get('WIKI_LOCKED_PAGES'): + return dict(error=True, message="Page is locked") - g.current_wiki.revert_page(name, commit, message="Reverting %s" % cname, - username=current_user.username) - flash('Page reverted', 'success') - return redirect(url_for('wiki.page', name=cname)) + sha = g.current_wiki.revert_page(name, commit, message=message, + username=current_user.username) + if sha: + flash("Page reverted") + + return dict(sha=sha) @blueprint.route("/_history/") @@ -51,74 +54,40 @@ def history(name): return render_template('wiki/history.html', name=name, history=g.current_wiki.get_history(name)) -@blueprint.route("/_edit/", methods=['GET', 'POST']) +@blueprint.route("/_edit/") @login_required def edit(name): - data = g.current_wiki.get_page(name) cname = to_canonical(name) - if request.method == 'POST': - edit_cname = to_canonical(request.form['name']) + page = g.current_wiki.get_page(name) - if edit_cname in app.config['WIKI_LOCKED_PAGES']: - return redirect(url_for(app.config['ROOT_ENDPOINT'])) + if not page: + # Page doesn't exist + return redirect(url_for('wiki.create', name=cname)) - if edit_cname != cname.lower(): - g.current_wiki.rename_page(cname, edit_cname) - - g.current_wiki.write_page(edit_cname, - request.form['content'], - message=request.form['message'], - username=current_user.username) - else: - if data: - name = remove_ext(data['name']) - content = data.get('data') - g.assets['js'].append('editor.js') - return render_template('wiki/edit.html', - name=name, - content=content, - info=data.get('info'), - sha=data.get('sha'), - partials=data.get('partials')) - else: - return redirect(url_for('wiki.create', name=cname)) + name = remove_ext(page['name']) + g.assets['js'].append('editor.js') + return render_template('wiki/edit.html', + name=name, + content=page.get('data'), + info=page.get('info'), + sha=page.get('sha'), + partials=page.get('partials')) -@blueprint.route("/_delete/", methods=['POST']) -@login_required -def delete(name): - pass - - -@blueprint.route("/_create/", defaults={'name': None}, methods=['GET', 'POST']) -@blueprint.route("/_create/", methods=['GET', 'POST']) +@blueprint.route("/_create/", defaults={'name': None}) +@blueprint.route("/_create/") @login_required def create(name): - if request.method == 'POST': - cname = to_canonical(request.form['name']) + cname = to_canonical(name) if name else "" + if cname and g.current_wiki.get_page(cname): + # Page exists, edit instead + return redirect(url_for('wiki.edit', name=cname)) - if cname in app.config['WIKI_LOCKED_PAGES']: - return redirect(url_for("wiki.create")) - - if not cname: - return redirect(url_for("wiki.create")) - - g.current_wiki.write_page(request.form['name'], - request.form['content'], - message=request.form['message'], - create=True, - username=current_user.username) - else: - cname = to_canonical(name) if name else "" - if cname and g.current_wiki.get_page(cname): - # Page exists, edit instead - return redirect(url_for('wiki.edit', name=cname)) - - g.assets['js'].append('editor.js') - return render_template('wiki/edit.html', - name=cname, - content="", - info={}) + g.assets['js'].append('editor.js') + return render_template('wiki/edit.html', + name=cname, + content="", + info={}) @blueprint.route("/_index") @@ -126,6 +95,48 @@ def index(): return render_template('wiki/index.html', index=g.current_wiki.get_index()) +@blueprint.route("/", methods=['POST', 'PUT', 'DELETE']) +@login_required +def page_write(name): + cname = to_canonical(name) + + if not cname: + return dict(error=True, message="Invalid name") + + if request.method == 'POST': + # Create + if cname in app.config.get('WIKI_LOCKED_PAGES'): + return dict(error=True, message="Page is locked") + + sha = g.current_wiki.write_page(cname, + request.form['content'], + message=request.form['message'], + create=True, + username=current_user.username) + + elif request.method == 'PUT': + edit_cname = to_canonical(request.form['name']) + + if edit_cname in app.config.get('WIKI_LOCKED_PAGES'): + return dict(error=True, message="Page is locked") + + if edit_cname != cname.lower(): + g.current_wiki.rename_page(cname, edit_cname) + + sha = g.current_wiki.write_page(edit_cname, + request.form['content'], + message=request.form['message'], + username=current_user.username) + + return dict(sha=sha) + + else: + # DELETE + sha = g.current_wiki.delete_page(name, user=current_user) + + return dict(sha=sha) + + @blueprint.route("/", defaults={'name': 'home'}) @blueprint.route("/") def page(name): @@ -134,6 +145,7 @@ def page(name): return redirect(url_for('wiki.page', name=cname)) data = g.current_wiki.get_page(cname) + if data: return render_template('wiki/page.html', name=cname, page=data, partials=data.get('partials')) else: diff --git a/realms/static/humans.txt b/realms/static/humans.txt new file mode 100644 index 0000000..e69de29 diff --git a/realms/static/img/favicon.ico b/realms/static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ba48efe6ee71970583ce3f43d51c02bb6b73d24f GIT binary patch literal 32038 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" + data['message'] + ""); + } else { + location.href = path; + } }); } }); \ No newline at end of file diff --git a/realms/static/js/main.js b/realms/static/js/main.js new file mode 100644 index 0000000..5c7e375 --- /dev/null +++ b/realms/static/js/main.js @@ -0,0 +1,40 @@ +$(function(){ + $(".ajax-form").submit(function(e){ + e.preventDefault(); + + var submitting = 'submitting'; + + if ($(this).data(submitting)) { + return; + } + + $(this).data(submitting, 1); + + var action = $(this).attr('action'); + var method = $(this).attr('method'); + var redirect = $(this).data('redirect'); + var data = $(this).serialize(); + + var req = $.ajax({ + type: method, + url: action, + data: data, + dataType: 'json' + }); + + req.done(function() { + if (redirect) { + location.href = redirect; + } + }); + + req.fail(function(data, status, error) { + console.log(data); + }); + + req.always(function() { + $(this).removeData(submitting); + }); + + }); +}); \ No newline at end of file diff --git a/realms/static/robots.txt b/realms/static/robots.txt new file mode 100644 index 0000000..e69de29 diff --git a/realms/templates/wiki/create.html b/realms/templates/wiki/create.html deleted file mode 100644 index 6a7775c..0000000 --- a/realms/templates/wiki/create.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'layout.html' %} -{% block body %} - -
-
- - -
- -
- - -
- - -
- -{% endblock %} \ No newline at end of file diff --git a/realms/templates/wiki/page.html b/realms/templates/wiki/page.html index ce97782..bd40d7f 100644 --- a/realms/templates/wiki/page.html +++ b/realms/templates/wiki/page.html @@ -9,7 +9,7 @@ {% block body %} {% if commit %}
-
+