]>
git.ipfire.org Git - ipfire.org.git/blob - src/web/docs.py
90b0c42327d6b77fbded428b48941628a07bae9a
7 from . import ui_modules
9 class PageHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
12 return self
.get_argument("action", None)
14 def write_error(self
, status_code
, **kwargs
):
15 # Render a custom page for 404
16 if status_code
== 404:
17 self
.render("docs/404.html", **kwargs
)
20 # Otherwise raise this to one layer above
21 super().write_error(status_code
, **kwargs
)
23 @tornado.web
.removeslash
29 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
30 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
32 # Check if we are asked to render a certain revision
33 revision
= self
.get_argument("revision", None)
36 page
= self
.backend
.wiki
.get_page(path
, revision
=revision
)
39 if self
.action
== "diff":
41 a
= self
.get_argument("a")
42 b
= self
.get_argument("b")
44 # Fetch both versions of the page
45 a
= self
.backend
.wiki
.get_page(path
, revision
=a
)
46 b
= self
.backend
.wiki
.get_page(path
, revision
=b
)
48 raise tornado
.web
.HTTPError(404)
50 # Cannot render a diff for the identical page
52 raise tornado
.web
.HTTPError(400)
54 # Make sure that b is newer than a
58 self
.render("docs/diff.html", page
=page
, a
=a
, b
=b
)
62 elif self
.action
== "restore":
63 self
.render("docs/confirm-restore.html", page
=page
)
67 elif self
.action
== "revisions":
68 self
.render("docs/revisions.html", page
=page
)
71 # If the page does not exist, we send 404
72 if not page
or page
.was_deleted():
73 # Handle /start links which were in the format of DokuWiki
74 if path
.endswith("/start"):
75 # Strip /start from path
76 path
= path
[:-6] or "/"
78 # Redirect user to page if it exists
79 page
= self
.backend
.wiki
.get_page(path
)
80 if page
and not page
.was_deleted():
81 self
.redirect(page
.url
)
83 raise tornado
.web
.HTTPError(404)
85 # Fetch the latest revision
86 latest_revision
= page
.get_latest_revision()
89 self
.render("docs/page.html", page
=page
, latest_revision
=latest_revision
)
92 class FilesHandler(base
.BaseHandler
):
93 @tornado.web
.authenticated
99 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
100 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
102 files
= self
.backend
.wiki
.get_files(path
)
104 self
.render("docs/files/index.html", path
=path
, files
=files
)
107 class FileHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
110 return self
.get_argument("action", None)
112 async def get(self
, path
):
114 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
115 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
117 # Check if we are asked to render a certain revision
118 revision
= self
.get_argument("revision", None)
121 file = self
.backend
.wiki
.get_file_by_path(path
, revision
=revision
)
123 raise tornado
.web
.HTTPError(404, "Could not find %s" % path
)
126 if self
.action
== "detail":
129 for breadcrumb
, title
in self
.backend
.wiki
.make_breadcrumbs(path
):
130 page
= self
.backend
.wiki
.get_page(breadcrumb
)
134 self
.render("docs/files/detail.html", page
=page
, file=file)
138 size
= self
.get_argument_int("s", None)
140 # Check if image should be resized
141 if size
and file.is_bitmap_image():
142 # Send WEBP if the browser supports
143 if self
.browser_accepts("image/webp"):
146 # Fall back to the native format
150 blob
= await file.get_thumbnail(size
, format
=format
)
154 # Allow downstream to cache this for a year
156 self
.set_expires(31536000)
159 self
._deliver
_file
(blob
, filename
=file.filename
)
162 class EditHandler(base
.BaseHandler
):
163 @tornado.web
.authenticated
169 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
170 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
172 # Fetch the wiki page
173 page
= self
.backend
.wiki
.get_page(path
)
175 # Empty page if it was deleted
176 if page
and page
.was_deleted():
180 self
.render("docs/edit.html", page
=page
, path
=path
)
182 @tornado.web
.authenticated
183 def post(self
, path
):
188 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
189 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
191 content
= self
.get_argument("content", None)
192 changes
= self
.get_argument("changes")
194 # Create a new page in the database
195 with self
.db
.transaction():
196 page
= self
.backend
.wiki
.create_page(path
,
197 self
.current_user
, content
, changes
=changes
, address
=self
.get_remote_ip())
199 # Add user as a watcher if wanted
200 watch
= self
.get_argument("watch", False)
202 page
.add_watcher(self
.current_user
)
205 if page
.was_deleted():
208 self
.redirect(page
.url
)
212 Updates the search index after the page has been edited
214 # This is being executed in the background and after
215 # the response has been set to the client
216 with self
.db
.transaction():
217 self
.backend
.wiki
.refresh()
220 class RenderHandler(base
.BaseHandler
):
221 def check_xsrf_cookie(self
):
224 @tornado.web
.authenticated
225 @base.ratelimit(minutes
=5, requests
=180)
226 def post(self
, path
):
230 content
= self
.get_argument("content")
233 renderer
= self
.backend
.wiki
.render(path
, content
)
235 self
.finish(renderer
.html
)
238 class RestoreHandler(base
.BaseHandler
):
239 @tornado.web
.authenticated
240 @base.ratelimit(minutes
=60, requests
=24)
242 path
= self
.get_argument("path")
245 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
246 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
248 # Check if we are asked to render a certain revision
249 revision
= self
.get_argument("revision", None)
250 comment
= self
.get_argument("comment", None)
252 # Fetch the wiki page
253 page
= self
.backend
.wiki
.get_page(path
, revision
=revision
)
255 with self
.db
.transaction():
257 author
=self
.current_user
,
258 address
=self
.get_remote_ip(),
262 # Redirect back to page
263 self
.redirect(page
.page
)
266 class UploadHandler(base
.BaseHandler
):
267 @tornado.web
.authenticated
268 @base.ratelimit(minutes
=60, requests
=24)
270 path
= self
.get_argument("path")
273 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
274 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
277 filename
, data
, mimetype
= self
.get_file("file")
279 # Use filename from request if any
280 filename
= self
.get_argument("filename", filename
)
282 # XXX check valid mimetypes
284 with self
.db
.transaction():
285 file = self
.backend
.wiki
.upload(path
, filename
, data
,
286 mimetype
=mimetype
, author
=self
.current_user
,
287 address
=self
.get_remote_ip())
289 except TypeError as e
:
292 self
.redirect("/docs%s/_files" % path
)
295 class WatchHandler(base
.BaseHandler
):
296 @tornado.web
.authenticated
297 @base.ratelimit(minutes
=60, requests
=180)
298 def get(self
, path
, action
):
302 page
= self
.backend
.wiki
.get_page(path
)
304 raise tornado
.web
.HTTPError(404, "Page does not exist: %s" % path
)
307 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
308 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
310 with self
.db
.transaction():
311 if action
== "watch":
312 page
.add_watcher(self
.current_user
)
313 elif action
== "unwatch":
314 page
.remove_watcher(self
.current_user
)
316 # Redirect back to page
317 self
.redirect(page
.url
)
320 class DeleteFileHandler(base
.BaseHandler
):
321 @tornado.web
.authenticated
324 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
325 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
328 file = self
.backend
.wiki
.get_file_by_path(path
)
330 raise tornado
.web
.HTTPError(404, "Could not find %s" % path
)
332 # Check if this can be deleted
333 if not file.can_be_deleted():
334 raise tornado
.web
.HTTPError(400, "%s cannot be deleted" % file)
336 self
.render("docs/confirm-delete.html", file=file)
338 @tornado.web
.authenticated
339 @base.ratelimit(minutes
=60, requests
=24)
340 def post(self
, path
):
342 if not self
.backend
.wiki
.check_acl(path
, self
.current_user
):
343 raise tornado
.web
.HTTPError(403, "Access to %s not allowed for %s" % (path
, self
.current_user
))
346 file = self
.backend
.wiki
.get_file_by_path(path
)
348 raise tornado
.web
.HTTPError(404, "Could not find %s" % path
)
350 # Check if this can be deleted
351 if not file.can_be_deleted():
352 raise tornado
.web
.HTTPError(400, "%s cannot be deleted" % file)
354 with self
.db
.transaction():
355 file.delete(self
.current_user
)
357 self
.redirect("/docs%s/_files" % file.path
)
360 class SearchHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
361 @base.ratelimit(minutes
=5, requests
=25)
363 q
= self
.get_argument("q")
366 with self
.db
.transaction():
367 pages
= self
.backend
.wiki
.search(q
, account
=self
.current_user
, limit
=50)
369 self
.render("docs/search-results.html", q
=q
, pages
=pages
)
372 class RecentChangesHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
374 recent_changes
= self
.backend
.wiki
.get_recent_changes(self
.current_user
, limit
=50)
376 self
.render("docs/recent-changes.html", recent_changes
=recent_changes
)
379 class TreeHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
381 self
.render("docs/tree.html", pages
=self
.backend
.wiki
)
384 class WatchlistHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
385 @tornado.web
.authenticated
387 pages
= self
.backend
.wiki
.get_watchlist(self
.current_user
)
389 self
.render("docs/watchlist.html", pages
=pages
)
392 class ListModule(ui_modules
.UIModule
):
393 def render(self
, pages
, link_revision
=False, show_breadcrumbs
=True,
394 show_author
=True, show_changes
=False):
395 return self
.render_string("docs/modules/list.html", link_revision
=link_revision
,
396 pages
=pages
, show_breadcrumbs
=show_breadcrumbs
,
397 show_author
=show_author
, show_changes
=show_changes
)
400 class HeaderModule(ui_modules
.UIModule
):
404 Returns the path of the page (without any actions)
406 path
= self
.request
.path
.removeprefix("/docs")
408 return "/".join((p
for p
in path
.split("/") if not p
.startswith("_")))
410 def render(self
, suffix
=None):
411 _
= self
.locale
.translate
413 breadcrumbs
= self
.backend
.wiki
.make_breadcrumbs(self
.page
)
414 title
= self
.backend
.wiki
.get_page_title(self
.page
)
416 if self
.request
.path
.endswith("/_edit"):
418 elif self
.request
.path
.endswith("/_files"):
421 return self
.render_string("docs/modules/header.html",
422 breadcrumbs
=breadcrumbs
, page
=self
.page
, page_title
=title
, suffix
=suffix
)
425 class DiffModule(ui_modules
.UIModule
):
426 differ
= difflib
.Differ()
428 def render(self
, a
, b
):
429 diff
= self
.differ
.compare(
430 a
.markdown
.splitlines(),
431 b
.markdown
.splitlines(),
434 return self
.render_string("docs/modules/diff.html", diff
=diff
)