django-admin-wiki 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_admin_wiki-0.1.0/.gitignore +18 -0
- django_admin_wiki-0.1.0/LICENSE +29 -0
- django_admin_wiki-0.1.0/MANIFEST.in +10 -0
- django_admin_wiki-0.1.0/PKG-INFO +251 -0
- django_admin_wiki-0.1.0/README.md +205 -0
- django_admin_wiki-0.1.0/pyproject.toml +85 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/__init__.py +3 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/admin.py +89 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/apps.py +57 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/conf.py +94 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/fields.py +29 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/forms.py +84 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/management/__init__.py +0 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/__init__.py +0 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/import_mkdocs.py +234 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/refresh_wiki_search_vectors.py +15 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/setup_wiki_groups.py +21 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/media_storage.py +133 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/migrations/0001_initial.py +144 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/migrations/__init__.py +0 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/models.py +75 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/permissions.py +61 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/render.py +145 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/__init__.py +45 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/base.py +74 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/cloud.py +210 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/local.py +108 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/services.py +141 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/static/django_admin_wiki/summernote_upload.js +82 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/static/django_admin_wiki/wiki.css +231 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/admin/django_admin_wiki/wikipage/change_list.html +8 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/base.html +50 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/edit.html +61 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/home.html +24 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/includes/nav_items.html +12 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/page.html +18 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/search.html +22 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/templatetags/__init__.py +0 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/urls.py +16 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/views.py +263 -0
- django_admin_wiki-0.1.0/src/django_admin_wiki/widgets.py +25 -0
- django_admin_wiki-0.1.0/tests/__init__.py +0 -0
- django_admin_wiki-0.1.0/tests/settings.py +70 -0
- django_admin_wiki-0.1.0/tests/test_cloud_repo.py +116 -0
- django_admin_wiki-0.1.0/tests/test_wiki.py +284 -0
- django_admin_wiki-0.1.0/tests/urls.py +7 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Christophe Surbier
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include MANIFEST.in
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
recursive-include src/django_admin_wiki/templates *
|
|
6
|
+
recursive-include src/django_admin_wiki/static *
|
|
7
|
+
recursive-include src/django_admin_wiki/locale *
|
|
8
|
+
global-exclude __pycache__
|
|
9
|
+
global-exclude *.py[co]
|
|
10
|
+
global-exclude .DS_Store
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-admin-wiki
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Collaborative staff wiki for Django Admin — edit in the browser, search with Postgres.
|
|
5
|
+
Project-URL: Homepage, https://github.com/csurbier/the-django-admin-wiki
|
|
6
|
+
Project-URL: Documentation, https://github.com/csurbier/the-django-admin-wiki#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/csurbier/the-django-admin-wiki/issues
|
|
8
|
+
Author: Christophe Surbier
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: admin,django,fts,mkdocs,staff,wiki
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 4.2
|
|
16
|
+
Classifier: Framework :: Django :: 5.0
|
|
17
|
+
Classifier: Framework :: Django :: 5.1
|
|
18
|
+
Classifier: Framework :: Django :: 5.2
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Programming Language :: Python :: 3
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Requires-Dist: bleach>=6.0
|
|
32
|
+
Requires-Dist: django-summernote>=0.8.20
|
|
33
|
+
Requires-Dist: django<6,>=4.2
|
|
34
|
+
Requires-Dist: markdown>=3.5
|
|
35
|
+
Requires-Dist: pyyaml>=6.0
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest-django>=4.8; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
40
|
+
Provides-Extra: markdown-extras
|
|
41
|
+
Requires-Dist: pymdown-extensions>=10.0; extra == 'markdown-extras'
|
|
42
|
+
Provides-Extra: s3
|
|
43
|
+
Requires-Dist: boto3>=1.34; extra == 's3'
|
|
44
|
+
Requires-Dist: django-storages[s3]>=1.14; extra == 's3'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# django-admin-wiki
|
|
48
|
+
|
|
49
|
+
**Collaborative staff wiki for Django Admin — edit in the browser, search with Postgres.**
|
|
50
|
+
|
|
51
|
+
A reusable Django app for internal / back-office documentation next to `django.contrib.admin`.
|
|
52
|
+
Not a public MediaWiki clone.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- In-browser **WYSIWYG editing** (Summernote by default; switchable to textarea or a custom widget)
|
|
57
|
+
- Up to **three-level** navigation tree
|
|
58
|
+
- Full-text search (PostgreSQL FTS) with SQLite/MySQL substring fallback
|
|
59
|
+
- Revision history on every staff save
|
|
60
|
+
- Import from Markdown / MkDocs (`import_mkdocs`)
|
|
61
|
+
- Visible link from Django admin (Wiki pages changelist)
|
|
62
|
+
- **Premium cloud mode**: Hosted on our servers via API (no local wiki tables)
|
|
63
|
+
|
|
64
|
+
## Requirements
|
|
65
|
+
|
|
66
|
+
- Python ≥ 3.10
|
|
67
|
+
- Django ≥ 4.2, < 6
|
|
68
|
+
- PostgreSQL recommended for production search (`django.contrib.postgres`) when `STORAGE="local"`
|
|
69
|
+
|
|
70
|
+
## Install (5 minutes) — self-hosted (OSS)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install django-admin-wiki
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# settings.py
|
|
78
|
+
INSTALLED_APPS = [
|
|
79
|
+
...,
|
|
80
|
+
"django.contrib.postgres", # if using Postgres FTS
|
|
81
|
+
"django_summernote", # required for default WYSIWYG editor
|
|
82
|
+
"django_admin_wiki",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
MEDIA_URL = "/media/"
|
|
86
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
87
|
+
|
|
88
|
+
DJANGO_ADMIN_WIKI = {
|
|
89
|
+
"STORAGE": "local",
|
|
90
|
+
"SEARCH_BACKEND": "postgres", # or "fallback"
|
|
91
|
+
"SEARCH_CONFIG": "french",
|
|
92
|
+
"EDITOR": "summernote",
|
|
93
|
+
"ALLOW_MARKDOWN_READ": True,
|
|
94
|
+
"DOCS_DIR": BASE_DIR / "wiki_docs",
|
|
95
|
+
"MKDOCS_YML": BASE_DIR / "wiki_docs" / "mkdocs.yml",
|
|
96
|
+
"URL_PREFIX": "wiki/",
|
|
97
|
+
# Media: default local FS under DOCS_DIR/uploads via POST /wiki/media/upload/
|
|
98
|
+
# "MEDIA_BACKEND": "django", # use Django STORAGES (S3) instead
|
|
99
|
+
# "MEDIA_MAX_UPLOAD_BYTES": 5 * 1024 * 1024,
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Images in the editor are uploaded as files (no base64 in the database). Optional S3 for self-hosted:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install "django-admin-wiki[s3]"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
# settings — host-owned bucket
|
|
111
|
+
INSTALLED_APPS += ["storages"]
|
|
112
|
+
STORAGES = {
|
|
113
|
+
"default": {
|
|
114
|
+
"BACKEND": "storages.backends.s3.S3Storage",
|
|
115
|
+
"OPTIONS": { ... },
|
|
116
|
+
},
|
|
117
|
+
"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
|
|
118
|
+
}
|
|
119
|
+
DJANGO_ADMIN_WIKI = {..., "MEDIA_BACKEND": "django"}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# urls.py
|
|
124
|
+
path("summernote/", include("django_summernote.urls")),
|
|
125
|
+
path("wiki/", include("django_admin_wiki.urls")),
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
python manage.py migrate
|
|
130
|
+
python manage.py setup_wiki_groups # creates "Wiki readers" + "Wiki editors"
|
|
131
|
+
python manage.py runserver
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then assign users to a group in Django Admin → Users (or Groups). `is_staff` alone does **not** grant wiki access.
|
|
135
|
+
|
|
136
|
+
## Premium — cloud storage (no local wiki tables)
|
|
137
|
+
|
|
138
|
+
Subscribe at [django-admin-wiki.enprod.fr](https://django-admin-wiki.enprod.fr). You receive write and read-only API keys from the account dashboard.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
INSTALLED_APPS = [..., "django_summernote", "django_admin_wiki"]
|
|
142
|
+
|
|
143
|
+
# Prevent host migrate from creating wiki tables:
|
|
144
|
+
MIGRATION_MODULES = {"django_admin_wiki": None}
|
|
145
|
+
|
|
146
|
+
DJANGO_ADMIN_WIKI = {
|
|
147
|
+
"STORAGE": "cloud",
|
|
148
|
+
"CLOUD_API_URL": "https://django-admin-wiki.enprod.fr",
|
|
149
|
+
"CLOUD_API_KEY": "daw_live_...", # write key (editors)
|
|
150
|
+
"CLOUD_API_KEY_READ": "daw_live_...", # read-only key (readers)
|
|
151
|
+
"EDITOR": "summernote",
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
path("summernote/", include("django_summernote.urls")),
|
|
157
|
+
path("wiki/", include("django_admin_wiki.urls")),
|
|
158
|
+
# Do NOT run migrations for django_admin_wiki
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Isolation: every API request is scoped by the tenant bound to your API key. Tenant A cannot read Tenant B data.
|
|
162
|
+
|
|
163
|
+
### Read vs write (client Django users)
|
|
164
|
+
|
|
165
|
+
| URL | Who can enter |
|
|
166
|
+
|-----|----------------|
|
|
167
|
+
| `/admin/` | Django Admin → needs `is_staff` (always) |
|
|
168
|
+
| `/wiki/` | membership in **Wiki readers** or **Wiki editors** (not `is_staff`) |
|
|
169
|
+
|
|
170
|
+
Rights follow the **logged-in user of your Django project** (session cookie). Cloud SaaS accounts (email/password on the Premium site) only manage billing / API keys.
|
|
171
|
+
|
|
172
|
+
After `migrate` (or `python manage.py setup_wiki_groups`), two groups exist:
|
|
173
|
+
|
|
174
|
+
| Group | Can |
|
|
175
|
+
|-------|-----|
|
|
176
|
+
| **Wiki readers** | open `/wiki/`, search, view pages |
|
|
177
|
+
| **Wiki editors** | readers + create / edit / upload media |
|
|
178
|
+
|
|
179
|
+
Assign users in Django Admin → Users. Superusers always have full wiki access.
|
|
180
|
+
|
|
181
|
+
**Premium cloud mode — dual API keys**
|
|
182
|
+
|
|
183
|
+
The host app holds two keys; the package picks one from `request.user`:
|
|
184
|
+
|
|
185
|
+
| User group | Key used |
|
|
186
|
+
|------------|----------|
|
|
187
|
+
| Wiki editors (`wiki_can_write`) | `CLOUD_API_KEY` |
|
|
188
|
+
| Wiki readers | `CLOUD_API_KEY_READ` |
|
|
189
|
+
|
|
190
|
+
Create both keys from the account dashboard. The cloud API rejects writes on a read-only key.
|
|
191
|
+
|
|
192
|
+
See [docs/PREMIUM.md](docs/PREMIUM.md).
|
|
193
|
+
|
|
194
|
+
### Change the editor
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
DJANGO_ADMIN_WIKI = {"EDITOR": "textarea"} # or "custom" + EDITOR_WIDGET
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Import existing MkDocs docs (local mode)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
python manage.py import_mkdocs
|
|
204
|
+
python manage.py refresh_wiki_search_vectors # Postgres
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Settings reference
|
|
208
|
+
|
|
209
|
+
| Key | Default | Description |
|
|
210
|
+
|-----|---------|-------------|
|
|
211
|
+
| `STORAGE` | `"local"` | `"local"` (host DB) or `"cloud"` (Premium API) |
|
|
212
|
+
| `CLOUD_API_URL` | `""` | Base URL of the cloud API |
|
|
213
|
+
| `CLOUD_API_KEY` | `""` | Bearer write key `daw_live_…` |
|
|
214
|
+
| `CLOUD_API_KEY_READ` | `""` | Bearer read-only key (readers in cloud mode) |
|
|
215
|
+
| `SEARCH_BACKEND` | `"postgres"` | local mode only |
|
|
216
|
+
| `SEARCH_CONFIG` | `"french"` | local Postgres FTS config |
|
|
217
|
+
| `EDITOR` | `"summernote"` | `summernote`, `textarea`, or `custom` |
|
|
218
|
+
| `EDITOR_WIDGET` | `None` | Dotted path when `EDITOR="custom"` |
|
|
219
|
+
| `ALLOW_MARKDOWN_READ` | `True` | Convert legacy Markdown on read |
|
|
220
|
+
| `DOCS_DIR` / `MKDOCS_YML` | `wiki_docs` | MkDocs import (local) |
|
|
221
|
+
| `HOME_SLUG` | `"index"` | Home page slug |
|
|
222
|
+
| `URL_PREFIX` | `"wiki/"` | Link rewrite prefix for import |
|
|
223
|
+
| `MAX_NAV_DEPTH` | `3` | Max menu depth (root → …) |
|
|
224
|
+
|
|
225
|
+
## Breaking choices (v1)
|
|
226
|
+
|
|
227
|
+
- Postgres recommended for local FTS; SQLite uses fallback search
|
|
228
|
+
- Summernote is the default editor
|
|
229
|
+
- Navigation is up to **three levels** (root → section → page)
|
|
230
|
+
- Cloud mode uses shared DB with `tenant_id` isolation (not DB-per-customer)
|
|
231
|
+
|
|
232
|
+
## Example project
|
|
233
|
+
|
|
234
|
+
From the repository root:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
python -m venv .venv && source .venv/bin/activate
|
|
238
|
+
pip install -e ".[dev]"
|
|
239
|
+
cd example_project
|
|
240
|
+
python manage.py migrate
|
|
241
|
+
python manage.py setup_wiki_groups
|
|
242
|
+
python manage.py import_mkdocs
|
|
243
|
+
python manage.py createsuperuser
|
|
244
|
+
python manage.py runserver
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
See [example_project/README.md](example_project/README.md).
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
BSD-3-Clause (OSS / local mode). Premium cloud hosting is a commercial subscription.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# django-admin-wiki
|
|
2
|
+
|
|
3
|
+
**Collaborative staff wiki for Django Admin — edit in the browser, search with Postgres.**
|
|
4
|
+
|
|
5
|
+
A reusable Django app for internal / back-office documentation next to `django.contrib.admin`.
|
|
6
|
+
Not a public MediaWiki clone.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- In-browser **WYSIWYG editing** (Summernote by default; switchable to textarea or a custom widget)
|
|
11
|
+
- Up to **three-level** navigation tree
|
|
12
|
+
- Full-text search (PostgreSQL FTS) with SQLite/MySQL substring fallback
|
|
13
|
+
- Revision history on every staff save
|
|
14
|
+
- Import from Markdown / MkDocs (`import_mkdocs`)
|
|
15
|
+
- Visible link from Django admin (Wiki pages changelist)
|
|
16
|
+
- **Premium cloud mode**: Hosted on our servers via API (no local wiki tables)
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- Python ≥ 3.10
|
|
21
|
+
- Django ≥ 4.2, < 6
|
|
22
|
+
- PostgreSQL recommended for production search (`django.contrib.postgres`) when `STORAGE="local"`
|
|
23
|
+
|
|
24
|
+
## Install (5 minutes) — self-hosted (OSS)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install django-admin-wiki
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
# settings.py
|
|
32
|
+
INSTALLED_APPS = [
|
|
33
|
+
...,
|
|
34
|
+
"django.contrib.postgres", # if using Postgres FTS
|
|
35
|
+
"django_summernote", # required for default WYSIWYG editor
|
|
36
|
+
"django_admin_wiki",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
MEDIA_URL = "/media/"
|
|
40
|
+
MEDIA_ROOT = BASE_DIR / "media"
|
|
41
|
+
|
|
42
|
+
DJANGO_ADMIN_WIKI = {
|
|
43
|
+
"STORAGE": "local",
|
|
44
|
+
"SEARCH_BACKEND": "postgres", # or "fallback"
|
|
45
|
+
"SEARCH_CONFIG": "french",
|
|
46
|
+
"EDITOR": "summernote",
|
|
47
|
+
"ALLOW_MARKDOWN_READ": True,
|
|
48
|
+
"DOCS_DIR": BASE_DIR / "wiki_docs",
|
|
49
|
+
"MKDOCS_YML": BASE_DIR / "wiki_docs" / "mkdocs.yml",
|
|
50
|
+
"URL_PREFIX": "wiki/",
|
|
51
|
+
# Media: default local FS under DOCS_DIR/uploads via POST /wiki/media/upload/
|
|
52
|
+
# "MEDIA_BACKEND": "django", # use Django STORAGES (S3) instead
|
|
53
|
+
# "MEDIA_MAX_UPLOAD_BYTES": 5 * 1024 * 1024,
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Images in the editor are uploaded as files (no base64 in the database). Optional S3 for self-hosted:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install "django-admin-wiki[s3]"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# settings — host-owned bucket
|
|
65
|
+
INSTALLED_APPS += ["storages"]
|
|
66
|
+
STORAGES = {
|
|
67
|
+
"default": {
|
|
68
|
+
"BACKEND": "storages.backends.s3.S3Storage",
|
|
69
|
+
"OPTIONS": { ... },
|
|
70
|
+
},
|
|
71
|
+
"staticfiles": {"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage"},
|
|
72
|
+
}
|
|
73
|
+
DJANGO_ADMIN_WIKI = {..., "MEDIA_BACKEND": "django"}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# urls.py
|
|
78
|
+
path("summernote/", include("django_summernote.urls")),
|
|
79
|
+
path("wiki/", include("django_admin_wiki.urls")),
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python manage.py migrate
|
|
84
|
+
python manage.py setup_wiki_groups # creates "Wiki readers" + "Wiki editors"
|
|
85
|
+
python manage.py runserver
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Then assign users to a group in Django Admin → Users (or Groups). `is_staff` alone does **not** grant wiki access.
|
|
89
|
+
|
|
90
|
+
## Premium — cloud storage (no local wiki tables)
|
|
91
|
+
|
|
92
|
+
Subscribe at [django-admin-wiki.enprod.fr](https://django-admin-wiki.enprod.fr). You receive write and read-only API keys from the account dashboard.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
INSTALLED_APPS = [..., "django_summernote", "django_admin_wiki"]
|
|
96
|
+
|
|
97
|
+
# Prevent host migrate from creating wiki tables:
|
|
98
|
+
MIGRATION_MODULES = {"django_admin_wiki": None}
|
|
99
|
+
|
|
100
|
+
DJANGO_ADMIN_WIKI = {
|
|
101
|
+
"STORAGE": "cloud",
|
|
102
|
+
"CLOUD_API_URL": "https://django-admin-wiki.enprod.fr",
|
|
103
|
+
"CLOUD_API_KEY": "daw_live_...", # write key (editors)
|
|
104
|
+
"CLOUD_API_KEY_READ": "daw_live_...", # read-only key (readers)
|
|
105
|
+
"EDITOR": "summernote",
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
path("summernote/", include("django_summernote.urls")),
|
|
111
|
+
path("wiki/", include("django_admin_wiki.urls")),
|
|
112
|
+
# Do NOT run migrations for django_admin_wiki
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Isolation: every API request is scoped by the tenant bound to your API key. Tenant A cannot read Tenant B data.
|
|
116
|
+
|
|
117
|
+
### Read vs write (client Django users)
|
|
118
|
+
|
|
119
|
+
| URL | Who can enter |
|
|
120
|
+
|-----|----------------|
|
|
121
|
+
| `/admin/` | Django Admin → needs `is_staff` (always) |
|
|
122
|
+
| `/wiki/` | membership in **Wiki readers** or **Wiki editors** (not `is_staff`) |
|
|
123
|
+
|
|
124
|
+
Rights follow the **logged-in user of your Django project** (session cookie). Cloud SaaS accounts (email/password on the Premium site) only manage billing / API keys.
|
|
125
|
+
|
|
126
|
+
After `migrate` (or `python manage.py setup_wiki_groups`), two groups exist:
|
|
127
|
+
|
|
128
|
+
| Group | Can |
|
|
129
|
+
|-------|-----|
|
|
130
|
+
| **Wiki readers** | open `/wiki/`, search, view pages |
|
|
131
|
+
| **Wiki editors** | readers + create / edit / upload media |
|
|
132
|
+
|
|
133
|
+
Assign users in Django Admin → Users. Superusers always have full wiki access.
|
|
134
|
+
|
|
135
|
+
**Premium cloud mode — dual API keys**
|
|
136
|
+
|
|
137
|
+
The host app holds two keys; the package picks one from `request.user`:
|
|
138
|
+
|
|
139
|
+
| User group | Key used |
|
|
140
|
+
|------------|----------|
|
|
141
|
+
| Wiki editors (`wiki_can_write`) | `CLOUD_API_KEY` |
|
|
142
|
+
| Wiki readers | `CLOUD_API_KEY_READ` |
|
|
143
|
+
|
|
144
|
+
Create both keys from the account dashboard. The cloud API rejects writes on a read-only key.
|
|
145
|
+
|
|
146
|
+
See [docs/PREMIUM.md](docs/PREMIUM.md).
|
|
147
|
+
|
|
148
|
+
### Change the editor
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
DJANGO_ADMIN_WIKI = {"EDITOR": "textarea"} # or "custom" + EDITOR_WIDGET
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Import existing MkDocs docs (local mode)
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python manage.py import_mkdocs
|
|
158
|
+
python manage.py refresh_wiki_search_vectors # Postgres
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Settings reference
|
|
162
|
+
|
|
163
|
+
| Key | Default | Description |
|
|
164
|
+
|-----|---------|-------------|
|
|
165
|
+
| `STORAGE` | `"local"` | `"local"` (host DB) or `"cloud"` (Premium API) |
|
|
166
|
+
| `CLOUD_API_URL` | `""` | Base URL of the cloud API |
|
|
167
|
+
| `CLOUD_API_KEY` | `""` | Bearer write key `daw_live_…` |
|
|
168
|
+
| `CLOUD_API_KEY_READ` | `""` | Bearer read-only key (readers in cloud mode) |
|
|
169
|
+
| `SEARCH_BACKEND` | `"postgres"` | local mode only |
|
|
170
|
+
| `SEARCH_CONFIG` | `"french"` | local Postgres FTS config |
|
|
171
|
+
| `EDITOR` | `"summernote"` | `summernote`, `textarea`, or `custom` |
|
|
172
|
+
| `EDITOR_WIDGET` | `None` | Dotted path when `EDITOR="custom"` |
|
|
173
|
+
| `ALLOW_MARKDOWN_READ` | `True` | Convert legacy Markdown on read |
|
|
174
|
+
| `DOCS_DIR` / `MKDOCS_YML` | `wiki_docs` | MkDocs import (local) |
|
|
175
|
+
| `HOME_SLUG` | `"index"` | Home page slug |
|
|
176
|
+
| `URL_PREFIX` | `"wiki/"` | Link rewrite prefix for import |
|
|
177
|
+
| `MAX_NAV_DEPTH` | `3` | Max menu depth (root → …) |
|
|
178
|
+
|
|
179
|
+
## Breaking choices (v1)
|
|
180
|
+
|
|
181
|
+
- Postgres recommended for local FTS; SQLite uses fallback search
|
|
182
|
+
- Summernote is the default editor
|
|
183
|
+
- Navigation is up to **three levels** (root → section → page)
|
|
184
|
+
- Cloud mode uses shared DB with `tenant_id` isolation (not DB-per-customer)
|
|
185
|
+
|
|
186
|
+
## Example project
|
|
187
|
+
|
|
188
|
+
From the repository root:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
python -m venv .venv && source .venv/bin/activate
|
|
192
|
+
pip install -e ".[dev]"
|
|
193
|
+
cd example_project
|
|
194
|
+
python manage.py migrate
|
|
195
|
+
python manage.py setup_wiki_groups
|
|
196
|
+
python manage.py import_mkdocs
|
|
197
|
+
python manage.py createsuperuser
|
|
198
|
+
python manage.py runserver
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
See [example_project/README.md](example_project/README.md).
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
BSD-3-Clause (OSS / local mode). Premium cloud hosting is a commercial subscription.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-admin-wiki"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Collaborative staff wiki for Django Admin — edit in the browser, search with Postgres."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "BSD-3-Clause"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Christophe Surbier" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["django", "wiki", "admin", "staff", "fts", "mkdocs"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Web Environment",
|
|
19
|
+
"Framework :: Django",
|
|
20
|
+
"Framework :: Django :: 4.2",
|
|
21
|
+
"Framework :: Django :: 5.0",
|
|
22
|
+
"Framework :: Django :: 5.1",
|
|
23
|
+
"Framework :: Django :: 5.2",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: BSD License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
|
+
]
|
|
36
|
+
dependencies = [
|
|
37
|
+
"Django>=4.2,<6",
|
|
38
|
+
"bleach>=6.0",
|
|
39
|
+
"markdown>=3.5",
|
|
40
|
+
"PyYAML>=6.0",
|
|
41
|
+
"django-summernote>=0.8.20",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
markdown-extras = ["pymdown-extensions>=10.0"]
|
|
46
|
+
s3 = ["django-storages[s3]>=1.14", "boto3>=1.34"]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=8.0",
|
|
49
|
+
"pytest-django>=4.8",
|
|
50
|
+
"ruff>=0.6",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://github.com/csurbier/the-django-admin-wiki"
|
|
55
|
+
Documentation = "https://github.com/csurbier/the-django-admin-wiki#readme"
|
|
56
|
+
Issues = "https://github.com/csurbier/the-django-admin-wiki/issues"
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.wheel]
|
|
59
|
+
packages = ["src/django_admin_wiki"]
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
62
|
+
"src/django_admin_wiki" = "django_admin_wiki"
|
|
63
|
+
|
|
64
|
+
[tool.hatch.build.targets.sdist]
|
|
65
|
+
include = [
|
|
66
|
+
"/src",
|
|
67
|
+
"/tests",
|
|
68
|
+
"/README.md",
|
|
69
|
+
"/LICENSE",
|
|
70
|
+
"/MANIFEST.in",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
75
|
+
pythonpath = ["src", "."]
|
|
76
|
+
testpaths = ["tests"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
line-length = 100
|
|
80
|
+
target-version = "py310"
|
|
81
|
+
src = ["src", "tests", "example_project"]
|
|
82
|
+
exclude = ["**/migrations/**", ".venv"]
|
|
83
|
+
|
|
84
|
+
[tool.ruff.lint]
|
|
85
|
+
select = ["E", "F", "I", "W"]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from django.urls import reverse
|
|
3
|
+
from django.utils.html import format_html
|
|
4
|
+
|
|
5
|
+
from django_admin_wiki.forms import get_editor_widget
|
|
6
|
+
from django_admin_wiki.models import WikiPage, WikiRevision
|
|
7
|
+
from django_admin_wiki.render import sanitize_wiki_html
|
|
8
|
+
from django_admin_wiki.repositories import is_cloud_storage
|
|
9
|
+
from django_admin_wiki.services import save_wiki_page
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WikiRevisionInline(admin.TabularInline):
|
|
13
|
+
model = WikiRevision
|
|
14
|
+
extra = 0
|
|
15
|
+
can_delete = False
|
|
16
|
+
readonly_fields = ("title", "author", "created_at", "content_preview")
|
|
17
|
+
fields = ("created_at", "author", "title", "content_preview")
|
|
18
|
+
|
|
19
|
+
def content_preview(self, obj):
|
|
20
|
+
text = obj.content or ""
|
|
21
|
+
return text[:200] + ("…" if len(text) > 200 else "")
|
|
22
|
+
|
|
23
|
+
content_preview.short_description = "Preview"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WikiPageAdmin(admin.ModelAdmin):
|
|
27
|
+
change_list_template = "admin/django_admin_wiki/wikipage/change_list.html"
|
|
28
|
+
list_display = (
|
|
29
|
+
"title",
|
|
30
|
+
"slug",
|
|
31
|
+
"parent",
|
|
32
|
+
"sort_order",
|
|
33
|
+
"updated_at",
|
|
34
|
+
"updated_by",
|
|
35
|
+
"open_wiki",
|
|
36
|
+
)
|
|
37
|
+
list_filter = ("parent",)
|
|
38
|
+
search_fields = ("title", "slug", "nav_label", "content")
|
|
39
|
+
prepopulated_fields = {"slug": ("title",)}
|
|
40
|
+
readonly_fields = ("created_at", "updated_at", "author", "updated_by")
|
|
41
|
+
inlines = [WikiRevisionInline]
|
|
42
|
+
fieldsets = (
|
|
43
|
+
(
|
|
44
|
+
None,
|
|
45
|
+
{
|
|
46
|
+
"fields": (
|
|
47
|
+
"title",
|
|
48
|
+
"slug",
|
|
49
|
+
"nav_label",
|
|
50
|
+
"parent",
|
|
51
|
+
"sort_order",
|
|
52
|
+
"content",
|
|
53
|
+
),
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
(
|
|
57
|
+
"Metadata",
|
|
58
|
+
{
|
|
59
|
+
"fields": ("author", "updated_by", "created_at", "updated_at"),
|
|
60
|
+
},
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def formfield_for_dbfield(self, db_field, request, **kwargs):
|
|
65
|
+
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
|
|
66
|
+
if db_field.name == "content" and formfield is not None:
|
|
67
|
+
formfield.widget = get_editor_widget()
|
|
68
|
+
return formfield
|
|
69
|
+
|
|
70
|
+
def open_wiki(self, obj):
|
|
71
|
+
url = reverse("django_admin_wiki:wiki_page", args=[obj.slug])
|
|
72
|
+
return format_html('<a href="{}" target="_blank">Open</a>', url)
|
|
73
|
+
|
|
74
|
+
open_wiki.short_description = "Wiki"
|
|
75
|
+
|
|
76
|
+
def save_model(self, request, obj, form, change):
|
|
77
|
+
obj.content = sanitize_wiki_html(obj.content or "")
|
|
78
|
+
save_wiki_page(obj, user=request.user, create_revision=True)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _register_admin():
|
|
82
|
+
# In cloud mode, local WikiPage tables are not used — skip ModelAdmin registration.
|
|
83
|
+
if is_cloud_storage():
|
|
84
|
+
return
|
|
85
|
+
if not admin.site.is_registered(WikiPage):
|
|
86
|
+
admin.site.register(WikiPage, WikiPageAdmin)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_register_admin()
|