aboutsummaryrefslogtreecommitdiffstats
path: root/pysite/views/wiki
diff options
context:
space:
mode:
Diffstat (limited to 'pysite/views/wiki')
-rw-r--r--pysite/views/wiki/__init__.py0
-rw-r--r--pysite/views/wiki/delete.py64
-rw-r--r--pysite/views/wiki/edit.py149
-rw-r--r--pysite/views/wiki/history/compare.py70
-rw-r--r--pysite/views/wiki/history/show.py41
-rw-r--r--pysite/views/wiki/index.py8
-rw-r--r--pysite/views/wiki/move.py84
-rw-r--r--pysite/views/wiki/page.py36
-rw-r--r--pysite/views/wiki/render.py62
-rw-r--r--pysite/views/wiki/robots_txt.py15
-rw-r--r--pysite/views/wiki/search.py66
-rw-r--r--pysite/views/wiki/sitemap_xml.py22
-rw-r--r--pysite/views/wiki/source.py42
-rw-r--r--pysite/views/wiki/special/__init__.py0
-rw-r--r--pysite/views/wiki/special/all_pages.py27
-rw-r--r--pysite/views/wiki/special/index.py7
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"