kioblog
A reusable blog app for Django, distributed on PyPI. Mount it under any URL prefix in an existing project and re-skin it by overriding templates.
Posts are written in Markdown. Fenced code blocks are highlighted with Pygments and wrapped in a documented HTML structure you style yourself, so the blog looks like the rest of your site rather than like a plugin.
- Markdown posts with syntax-highlighted code blocks and a generated table of contents
- Categories, tags, search, reading-time estimates, related posts and previous/next navigation
- Markdown editor in the Django admin with a live preview that renders through the same pipeline as the public page
robots.txtandsitemap.xmlout of the box
Requirements
- Python 3.8+
- Django 3.0+
Markdown, Pygments, django-markdownx and django-robots are installed
automatically as dependencies.
Install
pip install kioblog
1. Add the apps
INSTALLED_APPS = [
# ... your apps ...
"django.contrib.sites", # required by django-robots
"django.contrib.sitemaps",
"robots",
"markdownx",
"kioblog",
]
SITE_ID = 1
2. Register the context processors
Without these the sidebar and site-wide settings render empty.
TEMPLATES = [
{
# ...
"OPTIONS": {
"context_processors": [
# ... the Django defaults ...
"kioblog.context_preprocessors.kioblog_settings",
"kioblog.context_preprocessors.kioblog_categories",
],
},
},
]
3. Point uploads somewhere
UPLOAD_TO is where post cover images are stored, relative to MEDIA_ROOT.
from pathlib import Path # already imported in settings generated by Django 3.1+
UPLOAD_TO = "kioblog"
MEDIA_URL = "/media/"
# Wrapping BASE_DIR copes with either form it takes: a str in projects
# generated by Django 3.0, a Path in 3.1+. Bare `BASE_DIR / "media"` raises
# TypeError on the former.
MEDIA_ROOT = Path(BASE_DIR) / "media"
kioblog does not serve media itself, deliberately — that is your project's job. In development add the usual
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)to your ownurls.py.
4. Include the URLs
# A generated urls.py imports only `path`; `include` has to be added.
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("kioblog.urls")),
]
5. Configure the admin editor
This step is easy to miss and fails silently. kioblog mounts markdownx under its own prefix (it cannot know where you will mount it), but markdownx's JavaScript always posts to a site-absolute path. If these do not match your prefix, the live preview and image drag-and-drop 404 in the browser console while everything else looks fine.
Using blog/ from step 4:
MARKDOWNX_URLS_PATH = "/blog/markdownx/markdownify/"
MARKDOWNX_UPLOAD_URLS_PATH = "/blog/markdownx/upload/"
# Optional but recommended: makes the admin preview render exactly like the
# public page, code blocks and all.
MARKDOWNX_MARKDOWNIFY_FUNCTION = "kioblog.markdown.render.render_markdown"
Both endpoints require a logged-in staff user.
6. Migrate
python manage.py migrate
Then create posts from the Django admin.
Site-wide settings
The Meta model holds arbitrary key/value settings, editable in the admin and
available in every template as kioblog_settings. The bundled templates use:
| Key | Used for |
|---|---|
blog_title |
<title> fallback |
meta_title |
<meta name="title"> fallback |
meta_description |
<meta name="description"> fallback |
social_twitter, social_facebook, social_instagram, social_youtube, social_twitch |
Sidebar links |
URLs
Mounted relative to your prefix:
| Route | Name | Page |
|---|---|---|
/ |
kioblog-home |
Post list |
/page/<n> |
kioblog-page |
Post list, paginated (5 per page) |
/category/<slug> |
kioblog-category |
Category archive |
/category/<slug>/page/<n>/ |
kioblog-category-page |
Category archive, paginated |
/tag/<slug>/ |
kioblog-tag |
Tag archive |
/tag/<slug>/page/<n>/ |
kioblog-tag-page |
Tag archive, paginated |
/search/?q= |
kioblog-search |
Case-insensitive substring match on title, excerpt or content — no tokenising, stemming or ranking |
/<slug>/ |
kioblog-post |
Single post |
/robots.txt, /sitemap.xml |
SEO |
Writing posts
Post content is Markdown. Fenced code blocks take an info-string of
language or language:filename:
```python:accounts/views.py
def index(request):
return render(request, "index.html")
```
Headings are shifted down one level, on the assumption that the page title is
already the <h1>: # renders as <h2>, ## as <h3>, and so on. All of
them get an id and appear in post.toc.
Re-skinning
Templates
Override any of these by creating a file at the same path in your own templates directory:
kioblog/base.html
kioblog/home.html # post list, also used for category and tag archives
kioblog/post.html
kioblog/search.html
kioblog/includes/header.html
kioblog/includes/sidebar.html
kioblog/includes/footer.html
Useful things on Post when writing your own:
post.content_html |
Rendered Markdown — needs |safe |
post.toc |
Nested table of contents covering every heading — needs |safe |
post.display_excerpt |
The excerpt, falling back to the first rendered paragraph |
post.reading_time |
Estimated minutes |
post.tags.all |
Tags |
post.get_previous, post.get_next, post.related_posts |
Navigation |
Only the two marked above return HTML. display_excerpt is deliberately plain
text — entities are already decoded, so adding |safe to it would render any
markup the author typed instead of escaping it.
Context varies by template — only recent_posts is present everywhere, so
check this before relying on a variable:
| Template | Context |
|---|---|
home.html (post list) |
posts, page_range, recent_posts, featured_post, category |
home.html (tag archive) |
posts, page_range, recent_posts, tag |
search.html |
posts, query, count, recent_posts |
post.html |
post, prev_post, next_post, related_posts, recent_posts |
Note search.html has no page_range (results are not paginated) and
post.html has neither posts nor page_range. Django renders a missing
variable as empty rather than raising, so a paginator copied into the wrong
template disappears silently.
Code blocks
kioblog wraps each highlighted block in this structure:
<figure class="code-block" data-lang="python">
<div class="code-block__bar">
<span class="code-block__dots"><i></i><i></i><i></i></span>
<span class="code-block__file">accounts/views.py</span>
<span class="code-block__lang">python</span>
<button class="code-block__copy" type="button">Copy</button>
</div>
<div class="code-block__body">
<pre class="code-block__gutter" aria-hidden="true">1
2</pre>
<div class="highlight"><pre><span></span><code>…token spans…</code></pre></div>
</div>
</figure>
The code-block__* names are a stable contract — kioblog owns them and
won't rename them under you. code-block__file is omitted when the fence has
no filename. The line-number gutter is a separate element so that selecting
the code doesn't pick up the numbers.
Everything from .highlight inwards is Pygments' output, not kioblog's,
and is not promised to keep its exact shape across Pygments releases — note
the empty <span></span> it currently emits before <code>. Style it through
the token classes (.highlight .k, .s, .c1, …) rather than by walking the
tree; something like pre.firstElementChild would land on that empty span
today and may land elsewhere tomorrow.
kioblog ships token colours only, as a One Dark Pygments stylesheet:
{% load static %}
<link href="{% static 'kioblog/code.css' %}" rel="stylesheet">
Everything around the code — the bar, the traffic-light dots, the copy button —
is unstyled and yours to design. Swap code.css for your own .highlight
rules if you want a different palette. The copy button carries no JavaScript
either; wire it up how you like.
Licence
MIT — see LICENSE.txt.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kioblog-0.2.4.tar.gz.
File metadata
- Download URL: kioblog-0.2.4.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c643dbde2cd9017423a5a586275c239f31123a3f78b00ec20c74333c784ef36
|
|
| MD5 |
da6144cbfd3e1007f704f3b6495674f0
|
|
| BLAKE2b-256 |
af9c003bda5d9e781871eaad1a71658451cfd522f9175d56cfa25aa690870acc
|
Provenance
The following attestation bundles were made for kioblog-0.2.4.tar.gz:
Publisher:
publish.yml on Eric-Bujeque/kioblog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kioblog-0.2.4.tar.gz -
Subject digest:
6c643dbde2cd9017423a5a586275c239f31123a3f78b00ec20c74333c784ef36 - Sigstore transparency entry: 2256403374
- Sigstore integration time:
-
Permalink:
Eric-Bujeque/kioblog@ea0b4a5bd2d9fc31069ec54455a0ad185edd97a4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Eric-Bujeque
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ea0b4a5bd2d9fc31069ec54455a0ad185edd97a4 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file kioblog-0.2.4-py3-none-any.whl.
File metadata
- Download URL: kioblog-0.2.4-py3-none-any.whl
- Upload date:
- Size: 31.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d24c3ab830903a6b10e8690385776e762fd67a662b4c904f29230ba70a1c3556
|
|
| MD5 |
ce4dd7ac950fc20a47ab458b11ce30d6
|
|
| BLAKE2b-256 |
1d119e2fafdd18d6cb811e118742e46eb927b8562d9bc19f85b715769fc23158
|
Provenance
The following attestation bundles were made for kioblog-0.2.4-py3-none-any.whl:
Publisher:
publish.yml on Eric-Bujeque/kioblog
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kioblog-0.2.4-py3-none-any.whl -
Subject digest:
d24c3ab830903a6b10e8690385776e762fd67a662b4c904f29230ba70a1c3556 - Sigstore transparency entry: 2256403379
- Sigstore integration time:
-
Permalink:
Eric-Bujeque/kioblog@ea0b4a5bd2d9fc31069ec54455a0ad185edd97a4 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Eric-Bujeque
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ea0b4a5bd2d9fc31069ec54455a0ad185edd97a4 -
Trigger Event:
workflow_dispatch
-
Statement type: