drf-to-mkdoc 0.1.8__tar.gz → 0.2.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.
Potentially problematic release.
This version of drf-to-mkdoc might be problematic. Click here for more details.
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/PKG-INFO +1 -1
- drf_to_mkdoc-0.2.0/docs/customizing_endpoints.md +173 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/docs/serving_mkdocs_with_django.md +31 -16
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/conf/defaults.py +2 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/generate_docs.py +46 -71
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/generate_model_docs.py +4 -5
- drf_to_mkdoc-0.2.0/drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +879 -0
- drf_to_mkdoc-0.2.0/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +728 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/common.py +20 -15
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/endpoint_detail_generator.py +9 -1
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/model_detail_generator.py +23 -25
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc.egg-info/PKG-INFO +1 -1
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc.egg-info/SOURCES.txt +2 -0
- drf_to_mkdoc-0.1.8/docs/customizing_endpoints.md +0 -106
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/.github/workflows/publish.yaml +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/.pre-commit-config.yaml +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/CONTRIBUTING.md +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/LICENSE +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/MANIFEST.in +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/README.md +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/conf/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/conf/defaults.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/conf/settings.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/docs/mkdocs.yml +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/apps.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/conf/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/conf/settings.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/build_docs.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/generate_doc_json.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/update_doc_schema.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/accessibility.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/animations.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/filter-section.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/fixes.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/layout.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/loading.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/sections.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/stats.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/tags.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/animations.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/base.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-cards.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/endpoint_list_generator.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/extractors/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/utils/model_list_generator.py +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc.egg-info/dependency_links.txt +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc.egg-info/requires.txt +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc.egg-info/top_level.txt +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/pyproject.toml +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/setup.cfg +0 -0
- {drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drf-to-mkdoc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Generate Markdown API docs from Django/DRF OpenAPI schema for MkDocs
|
|
5
5
|
Author-email: Hossein Shayesteh <shayestehhs1@gmail.com>
|
|
6
6
|
Maintainer-email: Hossein Shayesteh <shayestehhs1@gmail.com>
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
|
|
2
|
+
# Customizing API Endpoint Documentation
|
|
3
|
+
|
|
4
|
+
`drf-to-mkdoc` automatically generates API documentation from your Django REST Framework (DRF) project using the OpenAPI schema from **DRF Spectacular**. You can refine and extend that documentation using a **custom JSON file**.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Where to put your custom schema
|
|
9
|
+
|
|
10
|
+
By default, the generator looks for:
|
|
11
|
+
docs/configs/custom\_schema.json
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
You can change this path by setting `CUSTOM_SCHEMA_FILE` in your `DRF_TO_MKDOC` settings.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. JSON File Format
|
|
21
|
+
|
|
22
|
+
The file should be a JSON object where **keys are `operationId`s** from your OpenAPI schema. Each key can override or extend the operation’s documentation.
|
|
23
|
+
|
|
24
|
+
Supported fields for each operation:
|
|
25
|
+
|
|
26
|
+
- `description` → Text description of the endpoint
|
|
27
|
+
- `parameters` → Array of OpenAPI parameter objects
|
|
28
|
+
- `requestBody` → OpenAPI RequestBody object
|
|
29
|
+
- `responses` → OpenAPI Responses object
|
|
30
|
+
- `append_fields` → A list of keys that should **append to existing lists instead of replacing them**.
|
|
31
|
+
- Currently, this is only useful for fields that are arrays in the schema (e.g., `parameters`).
|
|
32
|
+
- If the target field is not a list (like `description`, `responses`, or `requestBody`), `append_fields` is ignored and the value is replaced as usual.
|
|
33
|
+
- Example: If you want to **keep auto-generated query parameters** and add your own, include `"parameters"` in `append_fields`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### Example `custom_schema.json` using all supported keys
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"clinic_panel_appointments_available_appointment_times_list": {
|
|
42
|
+
"description": "Shows all available appointment times for a clinic.",
|
|
43
|
+
"parameters": [
|
|
44
|
+
{
|
|
45
|
+
"name": "date",
|
|
46
|
+
"in": "query",
|
|
47
|
+
"description": "Filter appointments by date",
|
|
48
|
+
"required": false,
|
|
49
|
+
"schema": { "type": "string", "format": "date" },
|
|
50
|
+
"queryparam_type": "filter_fields"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "search",
|
|
54
|
+
"in": "query",
|
|
55
|
+
"description": "Search appointments by doctor or patient name",
|
|
56
|
+
"required": false,
|
|
57
|
+
"schema": { "type": "string" },
|
|
58
|
+
"queryparam_type": "search_fields"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"requestBody": {
|
|
62
|
+
"description": "Request body for creating an appointment",
|
|
63
|
+
"required": true,
|
|
64
|
+
"content": {
|
|
65
|
+
"application/json": {
|
|
66
|
+
"schema": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"doctor_id": { "type": "integer" },
|
|
70
|
+
"patient_id": { "type": "integer" },
|
|
71
|
+
"date": { "type": "string", "format": "date" },
|
|
72
|
+
"time_slot": { "type": "string" }
|
|
73
|
+
},
|
|
74
|
+
"required": ["doctor_id", "patient_id", "date", "time_slot"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"responses": {
|
|
80
|
+
"200": {
|
|
81
|
+
"description": "List of available time slots",
|
|
82
|
+
"content": {
|
|
83
|
+
"application/json": {
|
|
84
|
+
"schema": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"items": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"date": { "type": "string", "description": "Date in DATE_FORMAT" },
|
|
90
|
+
"time_slots": {
|
|
91
|
+
"type": "array",
|
|
92
|
+
"items": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"properties": {
|
|
95
|
+
"start_datetime": { "type": "string", "description": "Start datetime" },
|
|
96
|
+
"end_datetime": { "type": "string", "description": "End datetime" }
|
|
97
|
+
},
|
|
98
|
+
"required": ["start_datetime", "end_datetime"]
|
|
99
|
+
},
|
|
100
|
+
"description": "Available time slots for the date"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"required": ["date", "time_slots"]
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"404": {
|
|
110
|
+
"description": "No appointments found for the given filters",
|
|
111
|
+
"content": {
|
|
112
|
+
"application/json": {
|
|
113
|
+
"schema": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": {
|
|
116
|
+
"detail": { "type": "string", "example": "Appointments not found" }
|
|
117
|
+
},
|
|
118
|
+
"required": ["detail"]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"append_fields": ["parameters"]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
````
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 3. Adding Query Parameters
|
|
132
|
+
|
|
133
|
+
If you add a **query parameter** (`"in": "query"`), include a `queryparam_type` so it’s categorized properly in the generated docs.
|
|
134
|
+
|
|
135
|
+
Supported `queryparam_type` values:
|
|
136
|
+
|
|
137
|
+
* `search_fields` → Used for search filters
|
|
138
|
+
* `filter_fields` → Standard filters
|
|
139
|
+
* `ordering_fields` → Sort fields
|
|
140
|
+
* `filter_backends` → Backend-specific filters
|
|
141
|
+
* `pagination_fields` → Pagination-related fields
|
|
142
|
+
|
|
143
|
+
> ⚠️ If `queryparam_type` is missing or invalid, the generator will raise an error.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 4. How the custom schema is applied
|
|
148
|
+
|
|
149
|
+
1. `drf-to-mkdoc` loads your OpenAPI schema.
|
|
150
|
+
2. It reads your `custom_schema.json`.
|
|
151
|
+
3. For each `operationId` in your JSON:
|
|
152
|
+
|
|
153
|
+
* Finds the corresponding endpoint in the schema
|
|
154
|
+
* Replaces fields like `description`, `responses`, `parameters`, etc.
|
|
155
|
+
* Appends items for fields listed in `append_fields` instead of replacing
|
|
156
|
+
4. Generates your markdown documentation using the merged schema
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 5. Finding `operationId`s
|
|
161
|
+
|
|
162
|
+
`operationId`s are generated by DRF Spectacular. You can find them by:
|
|
163
|
+
|
|
164
|
+
* Checking your API endpoint pages in the browser (each includes the `operationId`)
|
|
165
|
+
* Inspecting the OpenAPI JSON/YAML schema (via `/schema/` endpoint or export)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 6. Tips for smooth usage
|
|
170
|
+
|
|
171
|
+
* Keep `custom_schema.json` in version control so your team benefits.
|
|
172
|
+
* Start small: add descriptions first, then parameters, then responses.
|
|
173
|
+
* Use `append_fields` if you want to **add extra info** without overwriting auto-generated items.
|
|
@@ -65,30 +65,45 @@ class DocumentationView(View):
|
|
|
65
65
|
raise Http404("Invalid path")
|
|
66
66
|
|
|
67
67
|
file_path = (site_dir / path).resolve()
|
|
68
|
-
site_root = site_dir.resolve()
|
|
69
68
|
|
|
70
|
-
# Ensure file is
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
raise Http404("Documentation file not found")
|
|
69
|
+
# Ensure the file exists and is within the site directory
|
|
70
|
+
try:
|
|
71
|
+
file_path = file_path.resolve()
|
|
72
|
+
site_dir = site_dir.resolve()
|
|
75
73
|
|
|
74
|
+
# Security check: ensure file is within site directory
|
|
75
|
+
if not str(file_path).startswith(str(site_dir)):
|
|
76
|
+
return HttpResponseRedirect("/docs/404/index.html?error=access_denied")
|
|
77
|
+
|
|
78
|
+
if not file_path.exists() or not file_path.is_file():
|
|
79
|
+
return HttpResponseRedirect("/docs/404/index.html")
|
|
80
|
+
|
|
81
|
+
except (OSError, ValueError) as e:
|
|
82
|
+
return HttpResponseRedirect("/docs/404/index.html?error=invalid_file_path")
|
|
83
|
+
|
|
84
|
+
# Determine content type
|
|
76
85
|
content_type, _ = mimetypes.guess_type(str(file_path))
|
|
77
86
|
if content_type is None:
|
|
78
87
|
content_type = "application/octet-stream"
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
# Read and serve the file
|
|
90
|
+
try:
|
|
91
|
+
with Path(file_path).open("rb") as f:
|
|
92
|
+
response = HttpResponse(f.read(), content_type=content_type)
|
|
93
|
+
|
|
94
|
+
# Set appropriate headers
|
|
95
|
+
if content_type.startswith("text/html"):
|
|
96
|
+
response["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
|
97
|
+
response["Pragma"] = "no-cache"
|
|
98
|
+
response["Expires"] = "0"
|
|
99
|
+
else:
|
|
100
|
+
response["Cache-Control"] = "public, max-age=3600"
|
|
101
|
+
|
|
102
|
+
except OSError as e:
|
|
103
|
+
return HttpResponseRedirect("/docs/404/index.html?error=could_not_read_file")
|
|
88
104
|
else:
|
|
89
|
-
response
|
|
105
|
+
return response
|
|
90
106
|
|
|
91
|
-
return response
|
|
92
107
|
```
|
|
93
108
|
|
|
94
109
|
## Permission options
|
|
@@ -5,6 +5,8 @@ DEFAULTS = {
|
|
|
5
5
|
"MODEL_DOCS_FILE": "docs/model-docs.json", # Path to model documentation JSON file
|
|
6
6
|
"DOC_CONFIG_FILE": "docs/configs/doc_config.json", # Path to documentation configuration file
|
|
7
7
|
"CUSTOM_SCHEMA_FILE": "docs/configs/custom_schema.json", # Path to custom schema file
|
|
8
|
+
"PATH_PARAM_SUBSTITUTE_FUNCTION": None,
|
|
9
|
+
"PATH_PARAM_SUBSTITUTE_MAPPING": {},
|
|
8
10
|
# Django apps - required, no default
|
|
9
11
|
"DJANGO_APPS": None, # List of Django app names to process
|
|
10
12
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
1
|
from pathlib import Path
|
|
4
2
|
|
|
5
3
|
from django.core.management.base import BaseCommand
|
|
@@ -33,100 +31,77 @@ class Command(BaseCommand):
|
|
|
33
31
|
def handle(self, *args, **options):
|
|
34
32
|
self.stdout.write(self.style.SUCCESS("🚀 Starting documentation generation..."))
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
generate_models = not options["endpoints_only"]
|
|
35
|
+
generate_endpoints = not options["models_only"]
|
|
36
|
+
if not generate_models and not generate_endpoints:
|
|
37
|
+
self.stdout.write(
|
|
38
|
+
self.style.ERROR(
|
|
39
|
+
"❌ No outputs selected: --models-only and --endpoints-only cannot be used together"
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
docs_dir = self._setup_docs_directory()
|
|
44
|
+
models_data = self._load_models_data() if generate_models else {}
|
|
45
|
+
schema_data = self._load_schema_data() if generate_endpoints else {}
|
|
46
|
+
|
|
47
|
+
if generate_models and models_data:
|
|
48
|
+
self._generate_models_documentation(models_data, docs_dir)
|
|
38
49
|
|
|
39
|
-
if
|
|
40
|
-
self.
|
|
41
|
-
elif options["endpoints_only"]:
|
|
42
|
-
self._generate_endpoints_only()
|
|
43
|
-
else:
|
|
44
|
-
self._generate_all()
|
|
50
|
+
if generate_endpoints and schema_data:
|
|
51
|
+
self._generate_endpoints_documentation(schema_data, docs_dir)
|
|
45
52
|
|
|
46
53
|
self.stdout.write(self.style.SUCCESS("✅ Documentation generation complete!"))
|
|
47
54
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
def _setup_docs_directory(self):
|
|
56
|
+
docs_dir = Path(drf_to_mkdoc_settings.DOCS_DIR)
|
|
57
|
+
docs_dir.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
return docs_dir
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
def _load_models_data(self):
|
|
53
61
|
json_data = load_model_json_data()
|
|
54
62
|
models_data = json_data.get("models", {}) if json_data else {}
|
|
55
63
|
|
|
56
64
|
if not models_data:
|
|
57
65
|
self.stdout.write(self.style.WARNING("⚠️ No model data found"))
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
docs_dir = Path(drf_to_mkdoc_settings.DOCS_DIR)
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
generate_model_docs(models_data, docs_dir)
|
|
64
|
-
create_models_index(models_data, docs_dir)
|
|
67
|
+
return models_data
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# Load schema
|
|
73
|
-
schema = get_schema()
|
|
69
|
+
def _load_schema_data(self):
|
|
70
|
+
try:
|
|
71
|
+
schema = get_schema()
|
|
72
|
+
except Exception as e:
|
|
73
|
+
self.stdout.write(self.style.ERROR(f"❌ Failed to load OpenAPI schema: {e}"))
|
|
74
|
+
return {}
|
|
74
75
|
if not schema:
|
|
75
76
|
self.stdout.write(self.style.ERROR("❌ Failed to load OpenAPI schema"))
|
|
76
|
-
return
|
|
77
|
+
return {}
|
|
77
78
|
|
|
78
79
|
paths = schema.get("paths", {})
|
|
79
80
|
components = schema.get("components", {})
|
|
80
81
|
|
|
81
82
|
self.stdout.write(f"📊 Loaded {len(paths)} API paths")
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
return {"paths": paths, "components": components}
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
total_endpoints = generate_endpoint_files(endpoints_by_app, components)
|
|
88
|
-
create_endpoints_index(endpoints_by_app, docs_dir)
|
|
89
|
-
|
|
90
|
-
self.stdout.write(
|
|
91
|
-
self.style.SUCCESS(
|
|
92
|
-
f"✅ Generated {total_endpoints} endpoint files with Django view introspection"
|
|
93
|
-
)
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
def _generate_all(self):
|
|
97
|
-
"""Generate complete documentation"""
|
|
98
|
-
self.stdout.write("📚 Generating complete documentation...")
|
|
99
|
-
|
|
100
|
-
docs_dir = Path(drf_to_mkdoc_settings.DOCS_DIR)
|
|
101
|
-
|
|
102
|
-
# Load data
|
|
103
|
-
json_data = load_model_json_data()
|
|
104
|
-
models_data = json_data.get("models", {}) if json_data else {}
|
|
105
|
-
schema = get_schema()
|
|
106
|
-
|
|
107
|
-
if not schema:
|
|
108
|
-
self.stdout.write(self.style.ERROR("❌ Failed to load OpenAPI schema"))
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
paths = schema.get("paths", {})
|
|
112
|
-
components = schema.get("components", {})
|
|
113
|
-
|
|
114
|
-
self.stdout.write(f"📊 Loaded {len(paths)} API paths")
|
|
86
|
+
def _generate_models_documentation(self, models_data, docs_dir):
|
|
87
|
+
self.stdout.write("📋 Generating model documentation...")
|
|
115
88
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
self.stdout.write(self.style.WARNING(f"⚠️ Failed to generate model docs: {e}"))
|
|
89
|
+
try:
|
|
90
|
+
generate_model_docs(models_data)
|
|
91
|
+
create_models_index(models_data, docs_dir)
|
|
92
|
+
self.stdout.write(self.style.SUCCESS("✅ Model documentation generated"))
|
|
93
|
+
except Exception as e:
|
|
94
|
+
self.stdout.write(self.style.WARNING(f"⚠️ Failed to generate model docs: {e}"))
|
|
95
|
+
if hasattr(self, "_generating_all"):
|
|
124
96
|
self.stdout.write(self.style.WARNING("Continuing with endpoint generation..."))
|
|
125
|
-
|
|
126
|
-
self.stdout.write(self.style.WARNING("⚠️ No model data found"))
|
|
97
|
+
raise
|
|
127
98
|
|
|
128
|
-
|
|
99
|
+
def _generate_endpoints_documentation(self, schema_data, docs_dir):
|
|
129
100
|
self.stdout.write("🔗 Generating endpoint documentation...")
|
|
101
|
+
|
|
102
|
+
paths = schema_data["paths"]
|
|
103
|
+
components = schema_data["components"]
|
|
104
|
+
|
|
130
105
|
endpoints_by_app = parse_endpoints_from_schema(paths)
|
|
131
106
|
total_endpoints = generate_endpoint_files(endpoints_by_app, components)
|
|
132
107
|
create_endpoints_index(endpoints_by_app, docs_dir)
|
{drf_to_mkdoc-0.1.8 → drf_to_mkdoc-0.2.0}/drf_to_mkdoc/management/commands/generate_model_docs.py
RENAMED
|
@@ -100,7 +100,7 @@ class Command(BaseCommand):
|
|
|
100
100
|
"description": self.get_model_description(model),
|
|
101
101
|
"abstract": meta.abstract,
|
|
102
102
|
"proxy": meta.proxy,
|
|
103
|
-
"
|
|
103
|
+
"column_fields": {},
|
|
104
104
|
"relationships": {},
|
|
105
105
|
"meta_options": self.get_meta_options(meta),
|
|
106
106
|
"methods": self.get_model_methods(model),
|
|
@@ -111,10 +111,9 @@ class Command(BaseCommand):
|
|
|
111
111
|
if field.many_to_many or field.one_to_many or field.many_to_one or field.one_to_one:
|
|
112
112
|
# Handle relationships separately
|
|
113
113
|
model_doc["relationships"][field.name] = self.introspect_relationship(field)
|
|
114
|
-
|
|
115
|
-
# Handle
|
|
116
|
-
model_doc["
|
|
117
|
-
|
|
114
|
+
if not (field.one_to_many or field.many_to_many):
|
|
115
|
+
# Handle column fields
|
|
116
|
+
model_doc["column_fields"][field.name] = self.introspect_field(field)
|
|
118
117
|
return model_doc
|
|
119
118
|
|
|
120
119
|
def introspect_field(self, field):
|