diff options
Diffstat (limited to 'pysite/views/wiki')
| -rw-r--r-- | pysite/views/wiki/__init__.py | 0 | ||||
| -rw-r--r-- | pysite/views/wiki/delete.py | 64 | ||||
| -rw-r--r-- | pysite/views/wiki/edit.py | 149 | ||||
| -rw-r--r-- | pysite/views/wiki/history/compare.py | 70 | ||||
| -rw-r--r-- | pysite/views/wiki/history/show.py | 41 | ||||
| -rw-r--r-- | pysite/views/wiki/index.py | 8 | ||||
| -rw-r--r-- | pysite/views/wiki/move.py | 84 | ||||
| -rw-r--r-- | pysite/views/wiki/page.py | 36 | ||||
| -rw-r--r-- | pysite/views/wiki/render.py | 62 | ||||
| -rw-r--r-- | pysite/views/wiki/robots_txt.py | 15 | ||||
| -rw-r--r-- | pysite/views/wiki/search.py | 66 | ||||
| -rw-r--r-- | pysite/views/wiki/sitemap_xml.py | 22 | ||||
| -rw-r--r-- | pysite/views/wiki/source.py | 42 | ||||
| -rw-r--r-- | pysite/views/wiki/special/__init__.py | 0 | ||||
| -rw-r--r-- | pysite/views/wiki/special/all_pages.py | 27 | ||||
| -rw-r--r-- | pysite/views/wiki/special/index.py | 7 |
16 files changed, 0 insertions, 693 deletions
diff --git a/pysite/views/wiki/__init__.py b/pysite/views/wiki/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/pysite/views/wiki/__init__.py +++ /dev/null diff --git a/pysite/views/wiki/delete.py b/pysite/views/wiki/delete.py deleted file mode 100644 index 728570a9..00000000 --- a/pysite/views/wiki/delete.py +++ /dev/null @@ -1,64 +0,0 @@ -import datetime - -from flask import redirect, url_for -from werkzeug.exceptions import NotFound - -from pysite.base_route import RouteView -from pysite.constants import BotEventTypes, CHANNEL_MOD_LOG, EDITOR_ROLES -from pysite.decorators import csrf, require_roles -from pysite.mixins import DBMixin, RMQMixin - - -class DeleteView(RouteView, DBMixin, RMQMixin): - path = "/delete/<path:page>" # "path" means that it accepts slashes - name = "delete" - table_name = "wiki" - revision_table_name = "wiki_revisions" - - @require_roles(*EDITOR_ROLES) - def get(self, page): - obj = self.db.get(self.table_name, page) - - if obj: - title = obj.get("title", "") - - if obj.get("lock_expiry") and obj.get("lock_user") != self.user_data.get("user_id"): - lock_time = datetime.datetime.fromtimestamp(obj["lock_expiry"]) - if datetime.datetime.utcnow() < lock_time: - return self.render("wiki/page_in_use.html", page=page) - - return self.render("wiki/page_delete.html", page=page, title=title, can_edit=True) - else: - raise NotFound() - - @require_roles(*EDITOR_ROLES) - @csrf - def post(self, page): - obj = self.db.get(self.table_name, page) - - if not obj: - raise NotFound() - - self.db.delete(self.table_name, page) - self.db.delete(self.revision_table_name, page) - - revisions = self.db.filter(self.revision_table_name, lambda revision: revision["slug"] == page) - - for revision in revisions: - self.db.delete(self.revision_table_name, revision["id"]) - - self.audit_log(obj) - - return redirect(url_for("wiki.page", page="home"), code=303) # Redirect, ensuring a GET - - def audit_log(self, obj): - self.rmq_bot_event( - BotEventTypes.send_embed, - { - "target": CHANNEL_MOD_LOG, - "title": f"Page Deletion", - "description": f"**{obj['title']}** was deleted by **{self.user_data.get('username')}**", - "colour": 0x3F8DD7, # Light blue - "timestamp": datetime.datetime.now().isoformat() - } - ) diff --git a/pysite/views/wiki/edit.py b/pysite/views/wiki/edit.py deleted file mode 100644 index 949c9942..00000000 --- a/pysite/views/wiki/edit.py +++ /dev/null @@ -1,149 +0,0 @@ -import datetime -import html -import re - -from flask import redirect, request, url_for -from werkzeug.exceptions import BadRequest - -from pysite.base_route import RouteView -from pysite.constants import BotEventTypes, CHANNEL_MOD_LOG, DEBUG_MODE, EDITOR_ROLES -from pysite.decorators import csrf, require_roles -from pysite.mixins import DBMixin, RMQMixin -from pysite.rst import render - -STRIP_REGEX = re.compile(r"<[^<]+?>") - - -class EditView(RouteView, DBMixin, RMQMixin): - path = "/edit/<path:page>" # "path" means that it accepts slashes - name = "edit" - table_name = "wiki" - revision_table_name = "wiki_revisions" - - @require_roles(*EDITOR_ROLES) - def get(self, page): - rst = "" - title = "" - preview = "<p>Preview will appear here.</p>" - - obj = self.db.get(self.table_name, page) - - if obj: - rst = obj.get("rst", "") - title = obj.get("title", "") - preview = obj.get("html", preview) - - if obj.get("lock_expiry") and obj.get("lock_user") != self.user_data.get("user_id"): - lock_time = datetime.datetime.fromtimestamp(obj["lock_expiry"]) - if datetime.datetime.utcnow() < lock_time: - return self.render("wiki/page_in_use.html", page=page, can_edit=True) - - lock_expiry = datetime.datetime.utcnow() + datetime.timedelta(minutes=5) - - # There are a couple of cases where we will not need to lock a page. One of these is if the application is - # current set to debug mode. The other of these cases is if the page is empty, because if the page is empty - # we will only have a partially filled out page if the user quits before saving. - if obj: - if not DEBUG_MODE and obj.get("rst"): - self.db.insert( - self.table_name, - { - "slug": page, - "lock_expiry": lock_expiry.timestamp(), - "lock_user": self.user_data.get("user_id") - }, - conflict="update" - ) - - return self.render("wiki/page_edit.html", page=page, rst=rst, title=title, preview=preview, can_edit=True) - - @require_roles(*EDITOR_ROLES) - @csrf - def post(self, page): - rst = request.form.get("rst") - title = request.form["title"] - - if not rst or not rst.strip(): - raise BadRequest() - - if not title or not title.strip(): - raise BadRequest() - - rendered = render(rst) - - obj = { - "slug": page, - "title": request.form["title"], - "rst": rst, - "html": rendered["html"], - "text": html.unescape(STRIP_REGEX.sub("", rendered["html"]).strip()), - "headers": rendered["headers"] - } - - self.db.insert( - self.table_name, - obj, - conflict="replace" - ) - - if not DEBUG_MODE: - # Add the post to the revisions table - revision_payload = { - "slug": page, - "post": obj, - "date": datetime.datetime.utcnow().timestamp(), - "user": self.user_data.get("user_id") - } - - del revision_payload["post"]["slug"] - - current_revisions = self.db.filter(self.revision_table_name, lambda rev: rev["slug"] == page) - sorted_revisions = sorted(current_revisions, key=lambda rev: rev["date"], reverse=True) - - if len(sorted_revisions) > 0: - old_rev = sorted_revisions[0] - else: - old_rev = None - - new_rev = self.db.insert(self.revision_table_name, revision_payload)["generated_keys"][0] - - self.audit_log(page, new_rev, old_rev, obj) - - return redirect(url_for("wiki.page", page=page), code=303) # Redirect, ensuring a GET - - @require_roles(*EDITOR_ROLES) - @csrf - def patch(self, page): - current = self.db.get(self.table_name, page) - if not current: - return "", 404 - - if current.get("lock_expiry"): # If there is a lock present - - # If user patching is not the user with the lock end here - if current["lock_user"] != self.user_data.get("user_id"): - return "", 400 - new_lock = datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # New lock time, 5 minutes in future - self.db.insert(self.table_name, { - "slug": page, - "lock_expiry": new_lock.timestamp() - }, conflict="update") # Update with new lock time - return "", 204 - - def audit_log(self, page, new_id, old_data, new_data): - if not old_data: - link = f"https://wiki.pythondiscord.com/source/{page}" - else: - link = f"https://wiki.pythondiscord.com/history/compare/{old_data['id']}/{new_id}" - - self.rmq_bot_event( - BotEventTypes.send_embed, - { - "target": CHANNEL_MOD_LOG, - "title": "Page Edit", - "description": f"**{new_data['title']}** edited by **{self.user_data.get('username')}**. " - f"[View the diff here]({link})", - "colour": 0x3F8DD7, # Light blue - "timestamp": datetime.datetime.now().isoformat() - } - ) diff --git a/pysite/views/wiki/history/compare.py b/pysite/views/wiki/history/compare.py deleted file mode 100644 index 6411ab30..00000000 --- a/pysite/views/wiki/history/compare.py +++ /dev/null @@ -1,70 +0,0 @@ -import difflib - -from pygments import highlight -from pygments.formatters import HtmlFormatter -from pygments.lexers import DiffLexer -from werkzeug.exceptions import BadRequest, NotFound - -from pysite.base_route import RouteView -from pysite.constants import DEBUG_MODE, EDITOR_ROLES -from pysite.mixins import DBMixin - - -class CompareView(RouteView, DBMixin): - path = "/history/compare/<string:first_rev>/<string:second_rev>" - name = "history.compare" - - table_name = "wiki_revisions" - table_primary_key = "id" - - def get(self, first_rev, second_rev): - before = self.db.get(self.table_name, first_rev) - after = self.db.get(self.table_name, second_rev) - - if not (before and after): - raise NotFound() - - if before["date"] > after["date"]: # Check whether the before was created after the after - raise BadRequest() - - if before["id"] == after["id"]: # The same revision has been requested - raise BadRequest() - - before_text = before["post"]["rst"] - after_text = after["post"]["rst"] - - if not before_text.endswith("\n"): - before_text += "\n" - - if not after_text.endswith("\n"): - after_text += "\n" - - before_text = before_text.splitlines(keepends=True) - after_text = after_text.splitlines(keepends=True) - - if not before["slug"] == after["slug"]: - raise BadRequest() # The revisions are not from the same post - - diff = difflib.unified_diff(before_text, after_text, fromfile=f"{first_rev}.rst", tofile=f"{second_rev}.rst") - diff = "".join(diff) - diff = highlight(diff, DiffLexer(), HtmlFormatter()) - return self.render("wiki/compare_revision.html", - title=after["post"]["title"], - page=before["slug"], - diff=diff, - slug=before["slug"], - can_edit=self.is_staff()) - - def is_staff(self): - if DEBUG_MODE: - return True - if not self.logged_in: - return False - - roles = self.user_data.get("roles", []) - - for role in roles: - if role in EDITOR_ROLES: - return True - - return False diff --git a/pysite/views/wiki/history/show.py b/pysite/views/wiki/history/show.py deleted file mode 100644 index 00a1dc27..00000000 --- a/pysite/views/wiki/history/show.py +++ /dev/null @@ -1,41 +0,0 @@ -import datetime - -from werkzeug.exceptions import NotFound - -from pysite.base_route import RouteView -from pysite.constants import DEBUG_MODE, EDITOR_ROLES -from pysite.mixins import DBMixin - - -class RevisionsListView(RouteView, DBMixin): - path = "/history/show/<path:page>" - name = "history.show" - - table_name = "wiki_revisions" - table_primary_key = "id" - - def get(self, page): - results = self.db.filter(self.table_name, lambda revision: revision["slug"] == page) - if len(results) == 0: - raise NotFound() - - for result in results: - ts = datetime.datetime.fromtimestamp(result["date"]) - result["pretty_time"] = ts.strftime("%d %b %Y") - - results = sorted(results, key=lambda revision: revision["date"], reverse=True) - return self.render("wiki/revision_list.html", page=page, revisions=results, can_edit=self.is_staff()), 200 - - def is_staff(self): - if DEBUG_MODE: - return True - if not self.logged_in: - return False - - roles = self.user_data.get("roles", []) - - for role in roles: - if role in EDITOR_ROLES: - return True - - return False diff --git a/pysite/views/wiki/index.py b/pysite/views/wiki/index.py deleted file mode 100644 index 53a4d269..00000000 --- a/pysite/views/wiki/index.py +++ /dev/null @@ -1,8 +0,0 @@ -from pysite.base_route import RedirectView - - -class WikiView(RedirectView): - path = "/" - name = "index" - page = "wiki.page" - kwargs = {"page": "home"} diff --git a/pysite/views/wiki/move.py b/pysite/views/wiki/move.py deleted file mode 100644 index 095a1fdb..00000000 --- a/pysite/views/wiki/move.py +++ /dev/null @@ -1,84 +0,0 @@ -import datetime - -from flask import redirect, request, url_for -from werkzeug.exceptions import BadRequest, NotFound - -from pysite.base_route import RouteView -from pysite.constants import BotEventTypes, CHANNEL_MOD_LOG, EDITOR_ROLES -from pysite.decorators import csrf, require_roles -from pysite.mixins import DBMixin, RMQMixin - - -class MoveView(RouteView, DBMixin, RMQMixin): - path = "/move/<path:page>" # "path" means that it accepts slashes - name = "move" - table_name = "wiki" - revision_table_name = "wiki_revisions" - - @require_roles(*EDITOR_ROLES) - def get(self, page): - obj = self.db.get(self.table_name, page) - - if obj: - title = obj.get("title", "") - - if obj.get("lock_expiry") and obj.get("lock_user") != self.user_data.get("user_id"): - lock_time = datetime.datetime.fromtimestamp(obj["lock_expiry"]) - if datetime.datetime.utcnow() < lock_time: - return self.render("wiki/page_in_use.html", page=page, can_edit=True) - - return self.render("wiki/page_move.html", page=page, title=title, can_edit=True) - else: - raise NotFound() - - @require_roles(*EDITOR_ROLES) - @csrf - def post(self, page): - location = request.form.get("location") - - if not location or not location.strip(): - raise BadRequest() - - obj = self.db.get(self.table_name, page) - - if not obj: - raise NotFound() - - title = obj.get("title", "") - other_obj = self.db.get(self.table_name, location) - - if other_obj: - return self.render( - "wiki/page_move.html", page=page, title=title, - message=f"There's already a page at {location} - please pick a different location" - ) - - self.db.delete(self.table_name, page) - - # Move all revisions for the old slug to the new slug. - revisions = self.db.filter(self.revision_table_name, lambda revision: revision["slug"] == obj["slug"]) - - for revision in revisions: - revision["slug"] = location - self.db.insert(self.revision_table_name, revision, conflict="update") - - obj["slug"] = location - - self.db.insert(self.table_name, obj, conflict="update") - - self.audit_log(obj) - - return redirect(url_for("wiki.page", page=location), code=303) # Redirect, ensuring a GET - - def audit_log(self, obj): - self.rmq_bot_event( - BotEventTypes.send_embed, - { - "target": CHANNEL_MOD_LOG, - "title": "Wiki Page Move", - "description": f"**{obj['title']}** was moved by **{self.user_data.get('username')}** to " - f"**{obj['slug']}**", - "colour": 0x3F8DD7, # Light blue - "timestamp": datetime.datetime.now().isoformat() - } - ) diff --git a/pysite/views/wiki/page.py b/pysite/views/wiki/page.py deleted file mode 100644 index 26edfcc4..00000000 --- a/pysite/views/wiki/page.py +++ /dev/null @@ -1,36 +0,0 @@ -from flask import redirect, url_for -from werkzeug.exceptions import NotFound - -from pysite.base_route import RouteView -from pysite.constants import DEBUG_MODE, EDITOR_ROLES -from pysite.mixins import DBMixin - - -class PageView(RouteView, DBMixin): - path = "/wiki/<path:page>" # "path" means that it accepts slashes - name = "page" - table_name = "wiki" - - def get(self, page): - obj = self.db.get(self.table_name, page) - - if obj is None: - if self.is_staff(): - return redirect(url_for("wiki.edit", page=page)) - - raise NotFound() - return self.render("wiki/page_view.html", page=page, data=obj, can_edit=self.is_staff()) - - def is_staff(self): - if DEBUG_MODE: - return True - if not self.logged_in: - return False - - roles = self.user_data.get("roles", []) - - for role in roles: - if role in EDITOR_ROLES: - return True - - return False diff --git a/pysite/views/wiki/render.py b/pysite/views/wiki/render.py deleted file mode 100644 index 39bdd133..00000000 --- a/pysite/views/wiki/render.py +++ /dev/null @@ -1,62 +0,0 @@ -import re - -from docutils.utils import SystemMessage -from flask import jsonify -from schema import Schema - -from pysite.base_route import APIView -from pysite.constants import EDITOR_ROLES, ValidationTypes -from pysite.decorators import api_params, csrf, require_roles -from pysite.rst import render - -SCHEMA = Schema([{ - "data": str -}]) - -MESSAGE_REGEX = re.compile(r"<string>:(\d+): \([A-Z]+/\d\) (.*)", flags=re.S) - - -class RenderView(APIView): - path = "/render" # "path" means that it accepts slashes - name = "render" - - @csrf - @require_roles(*EDITOR_ROLES) - @api_params(schema=SCHEMA, validation_type=ValidationTypes.json) - def post(self, data): - if not len(data): - return jsonify({"error": "No data!"}) - - data = data[0]["data"] - try: - html = render(data)["html"] - - return jsonify({"data": html}) - except SystemMessage as e: - lines = str(e) - data = { - "error": lines, - "error_lines": [] - } - - if "\n" in lines: - lines = lines.split("\n") - else: - lines = [lines] - - for message in lines: - match = MESSAGE_REGEX.match(message) - - if match: - data["error_lines"].append( - { - "row": int(match.group(1)) - 3, - "column": 0, - "type": "error", - "text": match.group(2) - } - ) - - return jsonify(data) - except Exception as e: - return jsonify({"error": str(e)}) diff --git a/pysite/views/wiki/robots_txt.py b/pysite/views/wiki/robots_txt.py deleted file mode 100644 index 308fe2a2..00000000 --- a/pysite/views/wiki/robots_txt.py +++ /dev/null @@ -1,15 +0,0 @@ -from flask import Response, url_for - -from pysite.base_route import RouteView - - -class RobotsTXT(RouteView): - path = "/robots.txt" - name = "robots_txt" - - def get(self): - return Response( - self.render( - "robots.txt", sitemap_url=url_for("api.sitemap_xml", _external=True) - ), content_type="text/plain" - ) diff --git a/pysite/views/wiki/search.py b/pysite/views/wiki/search.py deleted file mode 100644 index 369da943..00000000 --- a/pysite/views/wiki/search.py +++ /dev/null @@ -1,66 +0,0 @@ -import html -import re - -from flask import redirect, request, url_for -from werkzeug.exceptions import BadRequest - -from pysite.base_route import RouteView -from pysite.decorators import csrf -from pysite.mixins import DBMixin - -STRIP_REGEX = re.compile(r"<[^<]+?>") - - -class SearchView(RouteView, DBMixin): - path = "/search" # "path" means that it accepts slashes - name = "search" - table_name = "wiki" - revision_table_name = "wiki_revisions" - - def get(self): - return self.render("wiki/search.html") - - @csrf - def post(self): - given_query = request.form.get("query") - - if not given_query or not given_query.strip(): - raise BadRequest() - - query = f"({re.escape(given_query)})" - - pages = self.db.filter( - self.table_name, - lambda doc: doc["text"].match(f"(?i){query}") - ) - - if len(pages) == 1: - slug = pages[0]["slug"] - return redirect(url_for("wiki.page", page=slug), code=303) - - for obj in pages: - text = obj["text"] - - matches = re.finditer(query, text, flags=re.IGNORECASE) - snippets = [] - - for match in matches: - start = match.start() - 50 - - if start < 0: - start = 0 - - end = match.end() + 50 - - if end > len(text): - end = len(text) - - match_text = text[start:end] - match_text = re.sub(query, r"<strong>\1</strong>", html.escape(match_text), flags=re.IGNORECASE) - - snippets.append(match_text.replace("\n", "<br />")) - - obj["matches"] = snippets - - pages = sorted(pages, key=lambda d: d["title"]) - return self.render("wiki/search_results.html", pages=pages, query=given_query) diff --git a/pysite/views/wiki/sitemap_xml.py b/pysite/views/wiki/sitemap_xml.py deleted file mode 100644 index 9b7f0980..00000000 --- a/pysite/views/wiki/sitemap_xml.py +++ /dev/null @@ -1,22 +0,0 @@ -from flask import Response, url_for - -from pysite.base_route import RouteView -from pysite.mixins import DBMixin - - -class SitemapXML(RouteView, DBMixin): - path = "/sitemap.xml" - name = "sitemap_xml" - table_name = "wiki" - - def get(self): - urls = [] - - for page in self.db.get_all(self.table_name): - urls.append({ - "change_frequency": "weekly", - "type": "url", - "url": url_for("wiki.page", page=page["slug"], _external=True) - }) - - return Response(self.render("sitemap.xml", urls=urls), content_type="application/xml") diff --git a/pysite/views/wiki/source.py b/pysite/views/wiki/source.py deleted file mode 100644 index 83674447..00000000 --- a/pysite/views/wiki/source.py +++ /dev/null @@ -1,42 +0,0 @@ -from flask import redirect, url_for -from pygments import highlight -from pygments.formatters.html import HtmlFormatter -from pygments.lexers import get_lexer_by_name -from werkzeug.exceptions import NotFound - -from pysite.base_route import RouteView -from pysite.constants import DEBUG_MODE, EDITOR_ROLES -from pysite.mixins import DBMixin - - -class PageView(RouteView, DBMixin): - path = "/source/<path:page>" # "path" means that it accepts slashes - name = "source" - table_name = "wiki" - - def get(self, page): - obj = self.db.get(self.table_name, page) - - if obj is None: - if self.is_staff(): - return redirect(url_for("wiki.edit", page=page, can_edit=False)) - - raise NotFound() - - rst = obj["rst"] - rst = highlight(rst, get_lexer_by_name("rst"), HtmlFormatter(preclass="code", linenos="inline")) - return self.render("wiki/page_source.html", page=page, data=obj, rst=rst, can_edit=self.is_staff()) - - def is_staff(self): - if DEBUG_MODE: - return True - if not self.logged_in: - return False - - roles = self.user_data.get("roles", []) - - for role in roles: - if role in EDITOR_ROLES: - return True - - return False diff --git a/pysite/views/wiki/special/__init__.py b/pysite/views/wiki/special/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/pysite/views/wiki/special/__init__.py +++ /dev/null diff --git a/pysite/views/wiki/special/all_pages.py b/pysite/views/wiki/special/all_pages.py deleted file mode 100644 index d2e02a72..00000000 --- a/pysite/views/wiki/special/all_pages.py +++ /dev/null @@ -1,27 +0,0 @@ -from pysite.base_route import RouteView -from pysite.mixins import DBMixin - - -class PageView(RouteView, DBMixin): - path = "/special/all_pages" - name = "special.all_pages" - table_name = "wiki" - - def get(self): - pages = self.db.pluck(self.table_name, "title", "slug") - pages = sorted(pages, key=lambda d: d.get("title", "No Title")) - - letters = {} - - for page in pages: - if "title" not in page: - page["title"] = "No Title" - - letter = page["title"][0].upper() - - if letter not in letters: - letters[letter] = [] - - letters[letter].append(page) - - return self.render("wiki/special_all.html", letters=letters) diff --git a/pysite/views/wiki/special/index.py b/pysite/views/wiki/special/index.py deleted file mode 100644 index ccfc7a5a..00000000 --- a/pysite/views/wiki/special/index.py +++ /dev/null @@ -1,7 +0,0 @@ -from pysite.base_route import TemplateView - - -class PageView(TemplateView): - path = "/special" - name = "special" - template = "wiki/special.html" |