Skip to content

Latest commit

 

History

89 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flask-Wiki

A lightweight, file-based wiki system built as a Flask extension. Create, edit, search, and manage wiki pages stored as Markdown files on the filesystem -- no database required.

Features

  • Markdown pages with metadata (title, tags)
  • Full-text search powered by Whoosh
  • File/image uploads
  • WikiLinks ([[Page Name]] syntax)
  • Multilingual support
  • Markdown editor with a server-rendered preview tab
  • No CDN: every front-end asset is served by the application
  • Customizable templates and permissions

Installation

pip install flask-wiki

Quick start

from flask import Flask
from flask_wiki import Wiki

app = Flask(__name__)
app.config["SECRET_KEY"] = "your-secret-key"
Wiki(app)

Or using the application factory pattern:

from flask import Flask
from flask_wiki import Wiki

wiki = Wiki()


def create_app():
    app = Flask(__name__)
    app.config["SECRET_KEY"] = "your-secret-key"
    wiki.init_app(app)
    return app

The wiki will be available at /help by default (configurable via WIKI_URL_PREFIX).

Initialize the search index

Before using search, initialize the Whoosh index:

flask flask_wiki init-index
flask flask_wiki index

How it works

Wiki pages are plain Markdown files stored in a content directory (./data by default). The URL structure mirrors the filesystem, and every page belongs to a language: /help/guides/setup maps to data/guides/setup_en.md for an English reader. See Internationalization.

Each page file has an optional metadata header followed by the Markdown body:

title: My Page Title
tags: setup, guide

# Content starts here

Regular markdown content...

The wiki registers a Flask Blueprint with routes for viewing, editing, searching, and managing pages. Uploaded files (images) are stored in a subfolder and served via middleware.

Permissions

Flask-Wiki uses a callable-based permission system. The host application provides functions that return True or False to control access. By default, everything is open (all lambdas return True).

There are four permission settings:

Setting Purpose
WIKI_READ_VIEW_PERMISSION Controls access to read routes (view pages, search). Returns 403 if False.
WIKI_EDIT_VIEW_PERMISSION Controls access to edit routes (edit, delete, upload). Returns 403 if False.
WIKI_READ_UI_PERMISSION Controls visibility of read-related UI elements in templates.
WIKI_EDIT_UI_PERMISSION Controls visibility of edit buttons/links in templates.

Each permission is a callable (no arguments) that is evaluated per-request. This lets you integrate with any authentication system -- Flask-Login, session-based auth, API tokens, etc.

Example: integrating with Flask-Login

from flask_login import current_user

app.config["WIKI_READ_VIEW_PERMISSION"] = lambda: current_user.is_authenticated
app.config["WIKI_EDIT_VIEW_PERMISSION"] = lambda: current_user.is_authenticated and current_user.has_role("editor")
app.config["WIKI_EDIT_UI_PERMISSION"] = app.config["WIKI_EDIT_VIEW_PERMISSION"]

The VIEW permissions are enforced server-side via route decorators. The UI permissions only toggle visibility of buttons and links in the templates -- they do not enforce access control on their own. Typically you'll set the UI permissions to match the view permissions, but you can separate them if needed (e.g., show a "log in to edit" button to anonymous users).

Configuration

Content & storage

Key Default Description
WIKI_HOME 'home' Default page for /
WIKI_URL_PREFIX '/help' URL prefix for the wiki blueprint
WIKI_CONTENT_DIR './data' Directory for Markdown files
WIKI_UPLOAD_FOLDER './data/files' Directory for uploaded images
WIKI_ALLOWED_EXTENSIONS {'png','jpg','jpeg','gif','svg'} Allowed upload types
WIKI_INDEX_DIR './index' Whoosh search index directory

A prefix with a variable part

WIKI_URL_PREFIX may carry variable parts, which is how an application keeps a reader inside a section of its own -- a tenant, an organisation, a language:

app.config["WIKI_URL_PREFIX"] = "/<org_code>/help"

The wiki serves /unifr/help/setup without ever knowing what org_code means: its views never see the value, and every URL it builds carries it back. That holds for the links of its templates, for the wikilinks of a page body, and therefore for the whole navigation -- a reader who enters through one prefix stays there.

Any URL rule syntax will do, a converter of your own included, as long as the name does not collide with an argument of the wiki views -- url and filename: "/<org:org_code>/help".

Building such a URL from outside the wiki, from a footer for instance, means passing the value: url_for('wiki.index', org_code='unifr').

The uploaded files hang from the static part of the prefix, /help/files/ in the example above, whatever the prefix a reader came through. A WSGI mount point carries no variable, and neither does the URL of an image written in a page.

Templates

All templates can be overridden by setting these config values to your own template paths:

Key Default
WIKI_BASE_TEMPLATE 'wiki/base.html'
WIKI_PAGE_TEMPLATE 'wiki/page.html'
WIKI_EDITOR_TEMPLATE 'wiki/editor.html'
WIKI_SEARCH_TEMPLATE 'wiki/search.html'
WIKI_FILES_TEMPLATE 'wiki/files.html'
WIKI_NOT_FOUND_TEMPLATE 'wiki/404.html'
WIKI_FORBIDDEN_TEMPLATE 'wiki/403.html'
WIKI_ICON_TEMPLATE 'wiki/icons/bootstrap.html'
WIKI_TOAST_TEMPLATE 'wiki/toast.html'

Front-end assets

The wiki needs no build step, no CDN and no vendored third-party asset. Its whole front-end comes from bootstrap-flask:

Asset Origin
Bootstrap 4, jQuery, Popper shipped by bootstrap-flask
Bootstrap Icons (SVG sprite) shipped by bootstrap-flask

Set BOOTSTRAP_SERVE_LOCAL = True so bootstrap-flask serves its own assets instead of a CDN, which is what makes the wiki work without internet access:

app.config["BOOTSTRAP_SERVE_LOCAL"] = True

Page layout

Every page — article, editor, file listing, search results, error — is laid out by the same three class names, which an application overriding one of the page templates has to keep:

Class Role
wiki-page the grid: one column on a narrow screen, article plus outline from 768px up
wiki-toc the table of contents, first in the source, placed on the right on a wide screen
wiki-content the main column: article, editor form or listing, with its header

The TOC precedes the article in the source so a narrow screen shows it first, and the grid moves it to the right-hand column on a wide one. It is pinned with position: sticky and scrolls on its own once it outgrows the viewport. A page without headings renders no wiki-toc at all, and the grid falls back to a single centered column.

Feedback

Feedback shares one channel: the messages flashed by the server and the ones the browser raises on its own are all toasts, stacked in a wiki-toasts container fixed to the top right. Their markup comes from the toast macro of WIKI_TOAST_TEMPLATE, and the toasts the wiki raises by itself live in wiki/toasts.html.

An application overriding WIKI_BASE_TEMPLATE renders its own stack. It includes wiki/toasts.html in it, so it never has to know the ids wiki.js reveals nor repeat their messages, and it renders the flashed messages itself if nothing else in the application already does:

{% raw %}<div class="wiki-toasts">
  {% for category, message in get_flashed_messages(with_categories=True) %}
  {{ toast(message, category, autoshow=True) }}
  {% endfor %}
  {%- include "wiki/toasts.html" %}
</div>{% endraw %}

To render those toasts as the rest of the application does, point WIKI_TOAST_TEMPLATE at a template of its own supplying a macro with the same signature — toast(message, category='message', id=None, autoshow=False), where id marks a toast kept hidden until a script reveals it, and autoshow one wiki.js reveals as soon as the page is ready:

app.config["WIKI_TOAST_TEMPLATE"] = "myapp/macros/toast.html"

Icons

Templates never name a glyph directly. They ask for an intentsearch, copy, edit, upload, delete, language, save — and WIKI_ICON_TEMPLATE supplies the markup for it:

Value Markup Assets needed
'wiki/icons/bootstrap.html' (default) inline SVG using the Bootstrap Icons sprite none, bootstrap-flask ships it
'wiki/icons/fontawesome.html' <i class="fa-solid fa-..."> Font Awesome 7, supplied by your application

The Font Awesome variant emits class names only; it bundles nothing. Use it in an application that already ships Font Awesome — through a webpack bundle, for instance — and the wiki icons match the rest of that application:

app.config["WIKI_ICON_TEMPLATE"] = "wiki/icons/fontawesome.html"

Any template exposing an icon(name) macro works, so an application needing different styles or a third icon set can point the key at its own file.

Pages are edited in a plain <textarea>; the Preview tab posts the body to wiki.preview and renders it with the same Markdown pipeline as a saved page, so WikiLinks, captions and syntax highlighting show up exactly as they will.

Internationalization

Key Default Description
WIKI_CURRENT_LANGUAGE lambda: 'en' Callable returning the current language code
WIKI_LANGUAGES {'en': 'English', 'fr': 'French', 'de': 'German', 'it': 'Italian'} Available languages
WIKI_FALLBACK_LANGUAGES every language of WIKI_LANGUAGES, in order Languages tried, in order, when a page has no variant in the current language

Every page belongs to a language: its filename carries a language code (page_fr.md, page_de.md), and /help/page serves the variant matching WIKI_CURRENT_LANGUAGE. Pages created or edited through the wiki are always saved with a language code.

When a page has no variant in the current language, the wiki walks WIKI_FALLBACK_LANGUAGES in order and serves the first translation it finds, with a banner telling the reader which language the page is displayed in. A page is only a 404 when it exists in no language at all. Set WIKI_FALLBACK_LANGUAGES = [] to disable the cascade.

Page listings (index, tags) and search results follow the same cascade: one entry per page, in the current language when it exists.

Files without a language code (page.md) are still served, as a last resort, for wikis created before language codes became mandatory. They are read-only: editing one writes the variant of the current language and leaves the original untouched. To migrate such a wiki, rename its files to page_<language>.md and re-run flask flask_wiki index.

Markdown

Key Default Description
WIKI_MARKDOWN_EXTENSIONS {'codehilite', 'fenced_code'} Additional Python-Markdown extensions

The extensions toc, meta, tables, and a built-in Bootstrap extension are always loaded.

An image that carries a title and stands alone in its paragraph is rendered as a captioned <figure>: the title becomes the caption, and the alt text keeps describing the image for those who cannot see it. An image without a title, or one sitting inside a sentence, is rendered as a plain <img>.

Development

Requirements

  • Python >=3.14,<3.15
  • uv

Setup

git clone <repo-url>
cd flask-wiki
uv sync --frozen

Run the example app

cd examples
uv run flask flask_wiki init-index
uv run flask flask_wiki index
uv run flask run --debug
# Visit http://localhost:5000/help

Run tests

uv run poe run_tests

License

BSD 3-Clause. See LICENSE for details.

About

Simple file based wiki for Flask.

Resources

Stars

15 stars

Watchers

9 watching

Forks

Releases

Used by

Contributors

Languages