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.
Files changed (46) hide show
  1. django_admin_wiki-0.1.0/.gitignore +18 -0
  2. django_admin_wiki-0.1.0/LICENSE +29 -0
  3. django_admin_wiki-0.1.0/MANIFEST.in +10 -0
  4. django_admin_wiki-0.1.0/PKG-INFO +251 -0
  5. django_admin_wiki-0.1.0/README.md +205 -0
  6. django_admin_wiki-0.1.0/pyproject.toml +85 -0
  7. django_admin_wiki-0.1.0/src/django_admin_wiki/__init__.py +3 -0
  8. django_admin_wiki-0.1.0/src/django_admin_wiki/admin.py +89 -0
  9. django_admin_wiki-0.1.0/src/django_admin_wiki/apps.py +57 -0
  10. django_admin_wiki-0.1.0/src/django_admin_wiki/conf.py +94 -0
  11. django_admin_wiki-0.1.0/src/django_admin_wiki/fields.py +29 -0
  12. django_admin_wiki-0.1.0/src/django_admin_wiki/forms.py +84 -0
  13. django_admin_wiki-0.1.0/src/django_admin_wiki/management/__init__.py +0 -0
  14. django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/__init__.py +0 -0
  15. django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/import_mkdocs.py +234 -0
  16. django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/refresh_wiki_search_vectors.py +15 -0
  17. django_admin_wiki-0.1.0/src/django_admin_wiki/management/commands/setup_wiki_groups.py +21 -0
  18. django_admin_wiki-0.1.0/src/django_admin_wiki/media_storage.py +133 -0
  19. django_admin_wiki-0.1.0/src/django_admin_wiki/migrations/0001_initial.py +144 -0
  20. django_admin_wiki-0.1.0/src/django_admin_wiki/migrations/__init__.py +0 -0
  21. django_admin_wiki-0.1.0/src/django_admin_wiki/models.py +75 -0
  22. django_admin_wiki-0.1.0/src/django_admin_wiki/permissions.py +61 -0
  23. django_admin_wiki-0.1.0/src/django_admin_wiki/render.py +145 -0
  24. django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/__init__.py +45 -0
  25. django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/base.py +74 -0
  26. django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/cloud.py +210 -0
  27. django_admin_wiki-0.1.0/src/django_admin_wiki/repositories/local.py +108 -0
  28. django_admin_wiki-0.1.0/src/django_admin_wiki/services.py +141 -0
  29. django_admin_wiki-0.1.0/src/django_admin_wiki/static/django_admin_wiki/summernote_upload.js +82 -0
  30. django_admin_wiki-0.1.0/src/django_admin_wiki/static/django_admin_wiki/wiki.css +231 -0
  31. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/admin/django_admin_wiki/wikipage/change_list.html +8 -0
  32. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/base.html +50 -0
  33. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/edit.html +61 -0
  34. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/home.html +24 -0
  35. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/includes/nav_items.html +12 -0
  36. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/page.html +18 -0
  37. django_admin_wiki-0.1.0/src/django_admin_wiki/templates/django_admin_wiki/search.html +22 -0
  38. django_admin_wiki-0.1.0/src/django_admin_wiki/templatetags/__init__.py +0 -0
  39. django_admin_wiki-0.1.0/src/django_admin_wiki/urls.py +16 -0
  40. django_admin_wiki-0.1.0/src/django_admin_wiki/views.py +263 -0
  41. django_admin_wiki-0.1.0/src/django_admin_wiki/widgets.py +25 -0
  42. django_admin_wiki-0.1.0/tests/__init__.py +0 -0
  43. django_admin_wiki-0.1.0/tests/settings.py +70 -0
  44. django_admin_wiki-0.1.0/tests/test_cloud_repo.py +116 -0
  45. django_admin_wiki-0.1.0/tests/test_wiki.py +284 -0
  46. django_admin_wiki-0.1.0/tests/urls.py +7 -0
@@ -0,0 +1,18 @@
1
+ *.py[cod]
2
+ __pycache__/
3
+ *.egg-info/
4
+ .eggs/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .DS_Store
11
+ *.sqlite3
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .mypy_cache/
15
+ htmlcov/
16
+ .coverage
17
+ example_project/db.sqlite3
18
+ *.log
@@ -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, &lt; 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, &lt; 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,3 @@
1
+ """django-admin-wiki — collaborative staff wiki for Django Admin."""
2
+
3
+ __version__ = "0.1.0"
@@ -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()