drf-to-mkdoc 0.1.6__py3-none-any.whl → 0.1.9__py3-none-any.whl
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/__init__.py +1 -1
- drf_to_mkdoc/apps.py +6 -2
- drf_to_mkdoc/conf/defaults.py +2 -1
- drf_to_mkdoc/conf/settings.py +11 -5
- drf_to_mkdoc/management/commands/build_docs.py +61 -19
- drf_to_mkdoc/management/commands/generate_docs.py +5 -5
- drf_to_mkdoc/management/commands/generate_model_docs.py +1 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css +69 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css +117 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css +119 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css +57 -50
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css +12 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css +64 -21
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/animations.css +25 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/base.css +83 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-cards.css +126 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css +57 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css +51 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css +46 -0
- drf_to_mkdoc/utils/common.py +39 -32
- drf_to_mkdoc/utils/{endpoint_generator.py → endpoint_detail_generator.py} +214 -384
- drf_to_mkdoc/utils/endpoint_list_generator.py +234 -0
- drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +12 -17
- drf_to_mkdoc/utils/{model_generator.py → model_detail_generator.py} +20 -51
- drf_to_mkdoc/utils/model_list_generator.py +67 -0
- {drf_to_mkdoc-0.1.6.dist-info → drf_to_mkdoc-0.1.9.dist-info}/METADATA +3 -25
- {drf_to_mkdoc-0.1.6.dist-info → drf_to_mkdoc-0.1.9.dist-info}/RECORD +30 -23
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/extra.css +0 -358
- {drf_to_mkdoc-0.1.6.dist-info → drf_to_mkdoc-0.1.9.dist-info}/WHEEL +0 -0
- {drf_to_mkdoc-0.1.6.dist-info → drf_to_mkdoc-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {drf_to_mkdoc-0.1.6.dist-info → drf_to_mkdoc-0.1.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from django.templatetags.static import static
|
|
5
|
+
|
|
6
|
+
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
7
|
+
from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EndpointsIndexGenerator:
|
|
11
|
+
def __init__(self, active_filters: list[str] | None = None):
|
|
12
|
+
self.active_filters = set(
|
|
13
|
+
active_filters
|
|
14
|
+
or [
|
|
15
|
+
"method",
|
|
16
|
+
"path",
|
|
17
|
+
"app",
|
|
18
|
+
"models",
|
|
19
|
+
"auth",
|
|
20
|
+
"roles",
|
|
21
|
+
"content_type",
|
|
22
|
+
"params",
|
|
23
|
+
"schema",
|
|
24
|
+
"pagination",
|
|
25
|
+
"ordering",
|
|
26
|
+
"search",
|
|
27
|
+
"tags",
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def create_endpoint_card(
|
|
32
|
+
self, endpoint: dict[str, Any], app_name: str, viewset_name: str
|
|
33
|
+
) -> str:
|
|
34
|
+
method = endpoint["method"]
|
|
35
|
+
path = endpoint["path"]
|
|
36
|
+
filename = endpoint["filename"]
|
|
37
|
+
view_class = extract_viewset_from_operation_id(endpoint["operation_id"])
|
|
38
|
+
|
|
39
|
+
link_url = f"{app_name}/{viewset_name.lower()}/{filename}".replace(".md", "/index.html")
|
|
40
|
+
data_attrs = f"""
|
|
41
|
+
data-method="{method.lower()}"
|
|
42
|
+
data-path="{path.lower()}"
|
|
43
|
+
data-app="{app_name.lower()}"
|
|
44
|
+
data-auth="{str(endpoint.get("auth_required", False)).lower()}"
|
|
45
|
+
data-pagination="{str(endpoint.get("pagination_support", False)).lower()}"
|
|
46
|
+
data-search="{str(bool(getattr(view_class, "search_fields", []))).lower()}"
|
|
47
|
+
data-ordering="{str(endpoint.get("ordering_support", False)).lower()}"
|
|
48
|
+
data-models="{" ".join(endpoint.get("related_models", [])).lower()}"
|
|
49
|
+
data-roles="{" ".join(endpoint.get("permission_roles", [])).lower()}"
|
|
50
|
+
data-content-type="{endpoint.get("content_type", "").lower()}"
|
|
51
|
+
data-tags="{" ".join(endpoint.get("tags", [])).lower()}"
|
|
52
|
+
data-schema="{" ".join(endpoint.get("schema_fields", [])).lower()}"
|
|
53
|
+
data-params="{" ".join(endpoint.get("query_parameters", [])).lower()}"
|
|
54
|
+
""".strip()
|
|
55
|
+
|
|
56
|
+
return f"""
|
|
57
|
+
<a href="{link_url}" class="endpoint-card" {data_attrs}>
|
|
58
|
+
<span class="method-badge method-{method.lower()}">{method}</span>
|
|
59
|
+
<span class="endpoint-path">{path}</span>
|
|
60
|
+
</a>
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def create_filter_section(self) -> str:
|
|
64
|
+
filter_fields = {
|
|
65
|
+
"method": """<div class="filter-group">
|
|
66
|
+
<label class="filter-label">HTTP Method</label>
|
|
67
|
+
<select id="filter-method" class="filter-select">
|
|
68
|
+
<option value="">All</option>
|
|
69
|
+
<option value="get">GET</option>
|
|
70
|
+
<option value="post">POST</option>
|
|
71
|
+
<option value="put">PUT</option>
|
|
72
|
+
<option value="patch">PATCH</option>
|
|
73
|
+
<option value="delete">DELETE</option>
|
|
74
|
+
</select>
|
|
75
|
+
</div>""",
|
|
76
|
+
"path": """<div class="filter-group">
|
|
77
|
+
<label class="filter-label">Endpoint Path</label>
|
|
78
|
+
<input type="text" id="filter-path" class="filter-input"
|
|
79
|
+
placeholder="Search path...">
|
|
80
|
+
</div>""",
|
|
81
|
+
"app": """<div class="filter-group">
|
|
82
|
+
<label class="filter-label">Django App</label>
|
|
83
|
+
<select id="filter-app" class="filter-select">
|
|
84
|
+
<option value="">All</option>
|
|
85
|
+
<!-- Dynamically filled -->
|
|
86
|
+
</select>
|
|
87
|
+
</div>""",
|
|
88
|
+
"models": """<div class="filter-group">
|
|
89
|
+
<label class="filter-label">Related Models</label>
|
|
90
|
+
<input type="text" id="filter-models" class="filter-input">
|
|
91
|
+
</div>""",
|
|
92
|
+
"auth": """<div class="filter-group">
|
|
93
|
+
<label class="filter-label">Authentication Required</label>
|
|
94
|
+
<select id="filter-auth" class="filter-select">
|
|
95
|
+
<option value="">All</option>
|
|
96
|
+
<option value="true">Yes</option>
|
|
97
|
+
<option value="false">No</option>
|
|
98
|
+
</select>
|
|
99
|
+
</div>""",
|
|
100
|
+
"roles": """<div class="filter-group">
|
|
101
|
+
<label class="filter-label">Permission Roles</label>
|
|
102
|
+
<input type="text" id="filter-roles" class="filter-input">
|
|
103
|
+
</div>""",
|
|
104
|
+
"content_type": """<div class="filter-group">
|
|
105
|
+
<label class="filter-label">Content Type</label>
|
|
106
|
+
<input type="text" id="filter-content-type" class="filter-input">
|
|
107
|
+
</div>""",
|
|
108
|
+
"params": """<div class="filter-group">
|
|
109
|
+
<label class="filter-label">Query Parameters</label>
|
|
110
|
+
<input type="text" id="filter-params" class="filter-input">
|
|
111
|
+
</div>""",
|
|
112
|
+
"schema": """<div class="filter-group">
|
|
113
|
+
<label class="filter-label">Schema Fields</label>
|
|
114
|
+
<input type="text" id="filter-schema" class="filter-input">
|
|
115
|
+
</div>""",
|
|
116
|
+
"pagination": """<div class="filter-group">
|
|
117
|
+
<label class="filter-label">Pagination Support</label>
|
|
118
|
+
<select id="filter-pagination" class="filter-select">
|
|
119
|
+
<option value="">All</option>
|
|
120
|
+
<option value="true">Yes</option>
|
|
121
|
+
<option value="false">No</option>
|
|
122
|
+
</select>
|
|
123
|
+
</div>""",
|
|
124
|
+
"ordering": """<div class="filter-group">
|
|
125
|
+
<label class="filter-label">Ordering Support</label>
|
|
126
|
+
<select id="filter-ordering" class="filter-select">
|
|
127
|
+
<option value="">All</option>
|
|
128
|
+
<option value="true">Yes</option>
|
|
129
|
+
<option value="false">No</option>
|
|
130
|
+
</select>
|
|
131
|
+
</div>""",
|
|
132
|
+
"search": """<div class="filter-group">
|
|
133
|
+
<label class="filter-label">Search Support</label>
|
|
134
|
+
<select id="filter-search" class="filter-select">
|
|
135
|
+
<option value="">All</option>
|
|
136
|
+
<option value="true">Yes</option>
|
|
137
|
+
<option value="false">No</option>
|
|
138
|
+
</select>
|
|
139
|
+
</div>""",
|
|
140
|
+
"tags": """<div class="filter-group">
|
|
141
|
+
<label class="filter-label">Tags</label>
|
|
142
|
+
<input type="text" id="filter-tags" class="filter-input">
|
|
143
|
+
</div>""",
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fields_html = "\n".join(
|
|
147
|
+
[html for key, html in filter_fields.items() if (key in self.active_filters)]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return f"""
|
|
151
|
+
<div class="filter-sidebar collapsed" id="filterSidebar">
|
|
152
|
+
<h3 class="filter-title">🔍 Filters</h3>
|
|
153
|
+
<div class="filter-grid">
|
|
154
|
+
{fields_html}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div class="filter-actions">
|
|
158
|
+
<button class="filter-apply" onclick="applyFilters()">Apply</button>
|
|
159
|
+
<button class="filter-clear" onclick="clearFilters()">Clear</button>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="filter-results">Showing 0 endpoints</div>
|
|
163
|
+
</div>
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
def create_endpoints_index(
|
|
167
|
+
self, endpoints_by_app: dict[str, list[dict[str, Any]]], docs_dir: Path
|
|
168
|
+
) -> None:
|
|
169
|
+
stylesheets = [
|
|
170
|
+
"stylesheets/endpoints/variables.css",
|
|
171
|
+
"stylesheets/endpoints/base.css",
|
|
172
|
+
"stylesheets/endpoints/theme-toggle.css",
|
|
173
|
+
"stylesheets/endpoints/filter-section.css",
|
|
174
|
+
"stylesheets/endpoints/layout.css",
|
|
175
|
+
"stylesheets/endpoints/endpoints-grid.css",
|
|
176
|
+
"stylesheets/endpoints/badges.css",
|
|
177
|
+
"stylesheets/endpoints/endpoint-content.css",
|
|
178
|
+
"stylesheets/endpoints/tags.css",
|
|
179
|
+
"stylesheets/endpoints/sections.css",
|
|
180
|
+
"stylesheets/endpoints/stats.css",
|
|
181
|
+
"stylesheets/endpoints/loading.css",
|
|
182
|
+
"stylesheets/endpoints/animations.css",
|
|
183
|
+
"stylesheets/endpoints/responsive.css",
|
|
184
|
+
"stylesheets/endpoints/accessibility.css",
|
|
185
|
+
"stylesheets/endpoints/fixes.css",
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
scripts = [
|
|
189
|
+
"javascripts/endpoints-filter.js",
|
|
190
|
+
]
|
|
191
|
+
prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
|
|
192
|
+
css_links = "\n".join(
|
|
193
|
+
f'<link rel="stylesheet" href="{static(prefix_path + path)}">'
|
|
194
|
+
for path in stylesheets
|
|
195
|
+
)
|
|
196
|
+
js_scripts = "\n".join(
|
|
197
|
+
f'<script src="{static(prefix_path + path)}" defer></script>' for path in scripts
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
content = f"""# API Endpoints
|
|
201
|
+
<!-- inject CSS and JS directly -->
|
|
202
|
+
{css_links}
|
|
203
|
+
{js_scripts}
|
|
204
|
+
|
|
205
|
+
<div class="main-content">
|
|
206
|
+
"""
|
|
207
|
+
content += self.create_filter_section()
|
|
208
|
+
|
|
209
|
+
for app_name, endpoints in endpoints_by_app.items():
|
|
210
|
+
content += f'<h2>{app_name.title()}</h2>\n<div class="endpoints-grid">\n'
|
|
211
|
+
for endpoint in endpoints:
|
|
212
|
+
viewset = endpoint["viewset"]
|
|
213
|
+
content += self.create_endpoint_card(endpoint, app_name, viewset)
|
|
214
|
+
content += "</div>\n"
|
|
215
|
+
|
|
216
|
+
content += "</div>\n"
|
|
217
|
+
output_path = docs_dir / "endpoints" / "index.md"
|
|
218
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
219
|
+
with Path(output_path).open("w", encoding="utf-8") as f:
|
|
220
|
+
f.write(content)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def create_endpoints_index(
|
|
224
|
+
endpoints_by_app: dict[str, list[dict[str, Any]]], docs_dir: Path
|
|
225
|
+
) -> None:
|
|
226
|
+
generator = EndpointsIndexGenerator(
|
|
227
|
+
active_filters=[
|
|
228
|
+
"method",
|
|
229
|
+
"path",
|
|
230
|
+
"app",
|
|
231
|
+
"search",
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
generator.create_endpoints_index(endpoints_by_app, docs_dir)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""Query parameter extraction utilities for Django views."""
|
|
4
|
-
|
|
5
1
|
from typing import Any
|
|
6
2
|
|
|
3
|
+
import django_filters
|
|
4
|
+
|
|
7
5
|
from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
|
|
8
6
|
|
|
9
7
|
|
|
@@ -51,7 +49,7 @@ def extract_query_parameters_from_view_filter_fields(view_class: Any) -> list[st
|
|
|
51
49
|
elif hasattr(view_class, "filterset_fields") and view_class.filterset_fields:
|
|
52
50
|
filter_fields = sorted(view_class.filterset_fields)
|
|
53
51
|
|
|
54
|
-
return filter_fields
|
|
52
|
+
return list(set(filter_fields))
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
def extract_query_parameters_from_view_ordering_fields(view_class: Any) -> list[str]:
|
|
@@ -116,8 +114,6 @@ def _extract_filterset_fields_from_class_attributes(filterset_class: Any) -> lis
|
|
|
116
114
|
fields = []
|
|
117
115
|
|
|
118
116
|
try:
|
|
119
|
-
import django_filters
|
|
120
|
-
|
|
121
117
|
# Get all class attributes, including inherited ones
|
|
122
118
|
for attr_name in dir(filterset_class):
|
|
123
119
|
# Skip private attributes and known non-filter attributes
|
|
@@ -186,22 +182,21 @@ def _extract_filterset_fields_from_internal_attrs(filterset_class: Any) -> list[
|
|
|
186
182
|
|
|
187
183
|
|
|
188
184
|
def _extract_filterset_fields_from_get_fields(filterset_class: Any) -> list[str]:
|
|
189
|
-
if not
|
|
185
|
+
if not (filterset_class._meta and filterset_class._meta.model):
|
|
190
186
|
# If the Meta class is not defined in the Filter class,
|
|
191
187
|
# the get_fields function is raise error
|
|
192
188
|
return []
|
|
193
|
-
fields = []
|
|
194
189
|
|
|
195
190
|
# Try get_fields() method if available (for dynamic filters)
|
|
196
|
-
if hasattr(filterset_class, "get_fields"):
|
|
197
|
-
|
|
198
|
-
filterset_fields = filterset_instance.get_fields()
|
|
199
|
-
if filterset_fields and hasattr(filterset_fields, "keys"):
|
|
200
|
-
for field in filterset_fields:
|
|
201
|
-
if field not in fields:
|
|
202
|
-
fields.append(field)
|
|
191
|
+
if not hasattr(filterset_class, "get_fields"):
|
|
192
|
+
return []
|
|
203
193
|
|
|
204
|
-
|
|
194
|
+
filterset_instance = filterset_class()
|
|
195
|
+
filterset_fields = filterset_instance.get_fields()
|
|
196
|
+
if not (filterset_fields and hasattr(filterset_fields, "keys")):
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
return list(set(filterset_fields))
|
|
205
200
|
|
|
206
201
|
|
|
207
202
|
def extract_filterset_fields(filterset_class: Any) -> list[str]:
|
|
@@ -1,55 +1,9 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
|
-
from
|
|
3
|
+
from django.templatetags.static import static
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
models_by_app = {}
|
|
9
|
-
for model_name, model_info in models_data.items():
|
|
10
|
-
app_name = model_info.get("app_label", model_name.split(".")[0])
|
|
11
|
-
class_name = model_info.get("name", model_name.split(".")[-1])
|
|
12
|
-
if app_name not in models_by_app:
|
|
13
|
-
models_by_app[app_name] = []
|
|
14
|
-
models_by_app[app_name].append((class_name, model_name, model_info))
|
|
15
|
-
|
|
16
|
-
content = """# Django Models\n\nThis section contains documentation for
|
|
17
|
-
all Django models in the system, organized by Django application.\n\n"""
|
|
18
|
-
|
|
19
|
-
app_descriptions = get_app_descriptions()
|
|
20
|
-
|
|
21
|
-
for app_name in sorted(models_by_app.keys()):
|
|
22
|
-
app_desc = app_descriptions.get(app_name, f"{app_name.title()} application models")
|
|
23
|
-
content += f'<div class="app-header">{app_name.title()} App</div>\n\n'
|
|
24
|
-
content += f"*{app_desc}*\n\n"
|
|
25
|
-
|
|
26
|
-
content += '<div class="model-cards">\n'
|
|
27
|
-
|
|
28
|
-
for class_name, _model_name, _model_info in sorted(models_by_app[app_name]):
|
|
29
|
-
content += f"""
|
|
30
|
-
<a href="{app_name}/{class_name.lower()}/"
|
|
31
|
-
class="model-card">{class_name}</a>\n
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
content += "</div>\n\n"
|
|
35
|
-
|
|
36
|
-
content += """## Model Relationships\n\nThe models are interconnected through foreign keys
|
|
37
|
-
and many-to-many relationships:\n\n- **Users** can be associated
|
|
38
|
-
with multiple **Clinics** through **ClinicUser**
|
|
39
|
-
\n- **Doctors** belong to **Clinics**
|
|
40
|
-
and offer **Services** through **DoctorService**
|
|
41
|
-
\n- **Appointments** connect **Patients**
|
|
42
|
-
with **Doctors** and **Services**
|
|
43
|
-
\n- **Schedules** define **Doctor** availability in specific **Rooms**
|
|
44
|
-
\n- **Rooms** belong to **Clinics** and host **Appointments**\n
|
|
45
|
-
\nEach model page contains detailed field documentation,
|
|
46
|
-
method signatures, and relationships to other models.\n"""
|
|
47
|
-
|
|
48
|
-
models_index_path = docs_dir / "models" / "index.md"
|
|
49
|
-
models_index_path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
-
|
|
51
|
-
with models_index_path.open("w", encoding="utf-8") as f:
|
|
52
|
-
f.write(content)
|
|
5
|
+
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
6
|
+
from drf_to_mkdoc.utils.common import get_model_description, write_file
|
|
53
7
|
|
|
54
8
|
|
|
55
9
|
def generate_model_docs(models_data: dict[str, Any]) -> None:
|
|
@@ -67,6 +21,7 @@ def generate_model_docs(models_data: dict[str, Any]) -> None:
|
|
|
67
21
|
|
|
68
22
|
|
|
69
23
|
def render_fields_table(fields: dict[str, Any]) -> str:
|
|
24
|
+
"""Render the fields table for a model."""
|
|
70
25
|
content = "## Fields\n\n"
|
|
71
26
|
content += "| Field | Type | Description | Extra |\n"
|
|
72
27
|
content += "|-------|------|-------------|-------|\n"
|
|
@@ -102,6 +57,7 @@ def render_fields_table(fields: dict[str, Any]) -> str:
|
|
|
102
57
|
|
|
103
58
|
|
|
104
59
|
def render_choices_tables(fields: dict[str, Any]) -> str:
|
|
60
|
+
"""Render choice tables for fields with choices."""
|
|
105
61
|
choice_tables = []
|
|
106
62
|
|
|
107
63
|
for field_name, field_info in fields.items():
|
|
@@ -138,10 +94,23 @@ def create_model_page(model_info: dict[str, Any]) -> str:
|
|
|
138
94
|
|
|
139
95
|
def _create_model_header(name: str, app_label: str, table_name: str, description: str) -> str:
|
|
140
96
|
"""Create the header section of the model documentation."""
|
|
97
|
+
stylesheets = [
|
|
98
|
+
"stylesheets/models/variables.css",
|
|
99
|
+
"stylesheets/models/base.css",
|
|
100
|
+
"stylesheets/models/model-tables.css",
|
|
101
|
+
"stylesheets/models/responsive.css",
|
|
102
|
+
]
|
|
103
|
+
prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
|
|
104
|
+
css_links = "\n".join(
|
|
105
|
+
f'<link rel="stylesheet" href="{static(prefix_path + path)}">' for path in stylesheets
|
|
106
|
+
)
|
|
141
107
|
return f"""# {name}
|
|
142
108
|
|
|
143
|
-
|
|
144
|
-
|
|
109
|
+
<!-- inject CSS directly -->
|
|
110
|
+
{css_links}
|
|
111
|
+
|
|
112
|
+
**App:** {app_label}
|
|
113
|
+
**Table:** `{table_name}`
|
|
145
114
|
|
|
146
115
|
## Description
|
|
147
116
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from django.templatetags.static import static
|
|
5
|
+
|
|
6
|
+
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
7
|
+
from drf_to_mkdoc.utils.common import get_app_descriptions
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def create_models_index(models_data: dict[str, Any], docs_dir: Path) -> None:
|
|
11
|
+
"""Create the main models index page that lists all models organized by app."""
|
|
12
|
+
models_by_app = {}
|
|
13
|
+
for model_name, model_info in models_data.items():
|
|
14
|
+
app_name = model_info.get("app_label", model_name.split(".")[0])
|
|
15
|
+
class_name = model_info.get("name", model_name.split(".")[-1])
|
|
16
|
+
if app_name not in models_by_app:
|
|
17
|
+
models_by_app[app_name] = []
|
|
18
|
+
models_by_app[app_name].append((class_name, model_name, model_info))
|
|
19
|
+
|
|
20
|
+
stylesheets = [
|
|
21
|
+
"stylesheets/models/variables.css",
|
|
22
|
+
"stylesheets/models/base.css",
|
|
23
|
+
"stylesheets/models/model-cards.css",
|
|
24
|
+
"stylesheets/models/responsive.css",
|
|
25
|
+
"stylesheets/models/animations.css",
|
|
26
|
+
]
|
|
27
|
+
prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
|
|
28
|
+
css_links = "\n".join(
|
|
29
|
+
f'<link rel="stylesheet" href="{static(prefix_path + path)}">' for path in stylesheets
|
|
30
|
+
)
|
|
31
|
+
content = f"""# Django Models
|
|
32
|
+
|
|
33
|
+
This section contains documentation for all Django models in the system, organized by Django application.
|
|
34
|
+
|
|
35
|
+
<!-- inject CSS directly -->
|
|
36
|
+
{css_links}
|
|
37
|
+
|
|
38
|
+
<div class="models-container">
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
app_descriptions = get_app_descriptions()
|
|
42
|
+
|
|
43
|
+
for app_name in sorted(models_by_app.keys()):
|
|
44
|
+
app_desc = app_descriptions.get(app_name, f"{app_name.title()} application models")
|
|
45
|
+
content += f'<div class="app-header">{app_name.title()} App</div>\n'
|
|
46
|
+
content += f'<div class="app-description">{app_desc}</div>\n\n'
|
|
47
|
+
|
|
48
|
+
content += '<div class="model-cards">\n'
|
|
49
|
+
|
|
50
|
+
for class_name, _model_name, _model_info in sorted(models_by_app[app_name]):
|
|
51
|
+
content += f"""
|
|
52
|
+
<a href="{app_name}/{class_name.lower()}/"
|
|
53
|
+
class="model-card">{class_name}</a>\n
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
content += "</div>\n\n"
|
|
57
|
+
|
|
58
|
+
content += """</div>
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Each model page contains detailed field documentation, method signatures, and relationships to other models."""
|
|
62
|
+
|
|
63
|
+
models_index_path = docs_dir / "models" / "index.md"
|
|
64
|
+
models_index_path.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
|
|
66
|
+
with models_index_path.open("w", encoding="utf-8") as f:
|
|
67
|
+
f.write(content)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drf-to-mkdoc
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
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>
|
|
@@ -101,30 +101,8 @@ DRF_TO_MKDOC = {
|
|
|
101
101
|
}
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
2. **Create MkDocs configuration**:
|
|
105
|
-
|
|
106
|
-
```yaml
|
|
107
|
-
# mkdocs.yml
|
|
108
|
-
site_name: Your API Documentation
|
|
109
|
-
theme:
|
|
110
|
-
name: material
|
|
111
|
-
features:
|
|
112
|
-
- navigation.tabs
|
|
113
|
-
- navigation.sections
|
|
114
|
-
- navigation.expand
|
|
115
|
-
- search.highlight
|
|
116
|
-
- search.share
|
|
117
|
-
|
|
118
|
-
plugins:
|
|
119
|
-
- search
|
|
120
|
-
|
|
121
|
-
nav:
|
|
122
|
-
- Home: index.md
|
|
123
|
-
- About: about.md
|
|
124
|
-
- Models: models/index.md
|
|
125
|
-
- API Endpoints: endpoints/index.md
|
|
126
|
-
- Pagination: pagination.md
|
|
127
|
-
```
|
|
104
|
+
2. **Create MkDocs configuration**:
|
|
105
|
+
Copy the [`docs/mkdocs.yml`](docs/mkdocs.yml) file to your project root and customize it as needed.
|
|
128
106
|
|
|
129
107
|
3. **Build documentation**:
|
|
130
108
|
|
|
@@ -1,43 +1,50 @@
|
|
|
1
|
-
drf_to_mkdoc/__init__.py,sha256=
|
|
2
|
-
drf_to_mkdoc/apps.py,sha256
|
|
1
|
+
drf_to_mkdoc/__init__.py,sha256=IbTW5uKQvJRG9ncHRuk_AGKHPn4ruxs5LqDpUFgUhws,180
|
|
2
|
+
drf_to_mkdoc/apps.py,sha256=-NrC_dRr6GmLmNQhkNh819B7V1SS4DYDv5JOR0TtuJM,560
|
|
3
3
|
drf_to_mkdoc/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
drf_to_mkdoc/conf/defaults.py,sha256=
|
|
5
|
-
drf_to_mkdoc/conf/settings.py,sha256=
|
|
4
|
+
drf_to_mkdoc/conf/defaults.py,sha256=DZiATgfjkyU_J1RgR_zAzPdOVp-F2o3LVwjb3hP6j0U,660
|
|
5
|
+
drf_to_mkdoc/conf/settings.py,sha256=OOna4jWNTEfROBprpKnEHVl5RcAYExmOf0gBMA0E_FY,1664
|
|
6
6
|
drf_to_mkdoc/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
drf_to_mkdoc/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
drf_to_mkdoc/management/commands/build_docs.py,sha256=
|
|
8
|
+
drf_to_mkdoc/management/commands/build_docs.py,sha256=71dDDbb-qjRZr0kugviHtf3W_MSPFVjxkJ0tlLn97zM,4037
|
|
9
9
|
drf_to_mkdoc/management/commands/generate_doc_json.py,sha256=mWdYgMbSeLP4iQZeUm2DxwYQmdGy8w05XTEfbT_nOJo,19833
|
|
10
|
-
drf_to_mkdoc/management/commands/generate_docs.py,sha256=
|
|
11
|
-
drf_to_mkdoc/management/commands/generate_model_docs.py,sha256=
|
|
10
|
+
drf_to_mkdoc/management/commands/generate_docs.py,sha256=LUvKeJQ_cDL48rEfbWyjDsYEwzkQEpxpEjqUm11zQJg,5011
|
|
11
|
+
drf_to_mkdoc/management/commands/generate_model_docs.py,sha256=A_Q6o10kfy-GN_ZDD9YS6jv3RTyxBy28DEsi5qKZZoU,13421
|
|
12
12
|
drf_to_mkdoc/management/commands/update_doc_schema.py,sha256=TtHVQxnVpB_VELRkVcdsDXDU5zXdguFleB1mXCDMAbg,2009
|
|
13
13
|
drf_to_mkdoc/static/drf-to-mkdoc/javascripts/endpoints-filter.js,sha256=KtfWroqsXg-wwmk36hpoH--M9WIW85EFNGeswMjFu4g,6138
|
|
14
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/extra.css,sha256=jR52Kyld8fbHCUSehgU0DONcGnjTkqihQqiAXkAvflU,8024
|
|
15
14
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/accessibility.css,sha256=DwCGPoaxaUvyifryrKqmkFDH06XBNf65kYsflMTbi0M,494
|
|
16
15
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/animations.css,sha256=61m9SLAbatVUNuFeTUTxktoMu9SskYcwFjTsHYbsCRo,393
|
|
17
16
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css,sha256=kUlUcf72uRw6x6Gn7cUq_aTuSGBbhumZ4Us-eBDM7DM,1251
|
|
18
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css,sha256=
|
|
19
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css,sha256=
|
|
20
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css,sha256=
|
|
17
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/base.css,sha256=K9lEjO-TEaUsFCaiuBwqDGrrjIJP8oBDh7igBGRTD3g,1839
|
|
18
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoint-content.css,sha256=1EKHfDeOSpNJe6l3mHzsBlLqLVCJn_1TKtEVfV7Dp88,3452
|
|
19
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/endpoints-grid.css,sha256=Ct4H_ma3tC_1ogw9IeSNmUDZFJwzQWjCM6GomNXV8ig,4115
|
|
21
20
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/filter-section.css,sha256=MDsGVwgFMhP28pegPLJFn3_GJqVSWZAoy6quz5_9Gz0,4424
|
|
22
21
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/fixes.css,sha256=MIPiwWXkSMt5I_q6jN6X5CvWTBmq1CERKZffhvf3sKM,720
|
|
23
22
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/layout.css,sha256=dyoTUJ5Y8njxdWE6t2G3nRS-bT0BT04UMat8K6yW2y4,637
|
|
24
23
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/loading.css,sha256=C_wZjMw5Cs2inpg892S0_E7_m5bT8BwZfVBRQfvIcKw,712
|
|
25
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css,sha256=
|
|
24
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/responsive.css,sha256=mzR4tB3XrCg6Ih-X5GEzXzZoSR0R0AEKArRIZ7LqiGU,1672
|
|
26
25
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/sections.css,sha256=xdrO6vUpthFFN1ESummoGuG5MPtE2d2lPsBOWuv-T6o,705
|
|
27
26
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/stats.css,sha256=0cDD8s63r6zQid_O1schNvfIwys1Y526xO6-B6s4Lxc,667
|
|
28
27
|
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/tags.css,sha256=dOw13qsvVjx9cibzgzXlOutXVosNp6LzFfEFKvumG8w,1785
|
|
29
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css,sha256=
|
|
30
|
-
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css,sha256=
|
|
28
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css,sha256=j1P5xDQDfos8jeVYz5s1jjEeujMlZtLi39OC6VeuMcA,1034
|
|
29
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/variables.css,sha256=Sg2vcQOHdpmEFDn43OeZcMIKxtr5EOEI_wISkCmtcSU,1895
|
|
30
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/animations.css,sha256=IrqN9vJKgaHAWk2PBRKKmFHgH-lQlw5YZvEOGDqYk_g,656
|
|
31
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/base.css,sha256=qdXDVScWoEvFbSRfjDlnxvQZBy2JFX9yXPngMWNSZ7o,1849
|
|
32
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-cards.css,sha256=IppCOptBoFeQCz2sc-exPrnxvsdDxcwYGM-foQrZoVU,2779
|
|
33
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/model-tables.css,sha256=8CSy8YdFOJ3lGZ3sTVz2jd8cIxIryubQFrwcygAw06w,1352
|
|
34
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/responsive.css,sha256=ygqyUtpiWchTBBIQil1C6mN0AC5xinLoP7whSKfBmwg,944
|
|
35
|
+
drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/models/variables.css,sha256=2HvyjnJrygSzdzpE-FYpo6FGhrYhmZ7NwDFAkADXQNg,1094
|
|
31
36
|
drf_to_mkdoc/utils/__init__.py,sha256=6dFTb07S6yIf-INMy0Mlgf5purNir687ZU9WZtITh4k,68
|
|
32
|
-
drf_to_mkdoc/utils/common.py,sha256=
|
|
33
|
-
drf_to_mkdoc/utils/
|
|
34
|
-
drf_to_mkdoc/utils/
|
|
37
|
+
drf_to_mkdoc/utils/common.py,sha256=SxjGe9MtyxTyroXP7QdUiW9QqZ1N7xPjHHaAI-hqYxc,12077
|
|
38
|
+
drf_to_mkdoc/utils/endpoint_detail_generator.py,sha256=mX3sklhWsd-FmOV-ePrhLpg5l8-Eu7V7qb5EL4Tq0FI,27650
|
|
39
|
+
drf_to_mkdoc/utils/endpoint_list_generator.py,sha256=P7pEHscHNHpjDuTXmAUPYV3_t8uGSdaYOgiDGGoCwYc,9705
|
|
40
|
+
drf_to_mkdoc/utils/model_detail_generator.py,sha256=P5kWO-5aWgvyPgzq1qXnt1380ExnrzlOw18zI57P9u0,8063
|
|
41
|
+
drf_to_mkdoc/utils/model_list_generator.py,sha256=7gTPwxOxlvvCfDgVPTbGPsLi1gnI47UjMqKc57qRmks,2409
|
|
35
42
|
drf_to_mkdoc/utils/extractors/__init__.py,sha256=BvC8gKOPVI9gU1Piw0jRhKQ2ER5s1moAxgq7ZYkJWNI,86
|
|
36
|
-
drf_to_mkdoc/utils/extractors/query_parameter_extractors.py,sha256=
|
|
43
|
+
drf_to_mkdoc/utils/extractors/query_parameter_extractors.py,sha256=gnDlvF-bxkTp2NcEQf0EZZAfmdQzUpedhZ7N1LAYQzU,8432
|
|
37
44
|
drf_to_mkdoc/utils/md_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
45
|
drf_to_mkdoc/utils/md_generators/query_parameters_generators.py,sha256=N-XqZ_FUODSR5V4xM9oEA3aaIiNGNmNwpvrWbQTx6RI,2566
|
|
39
|
-
drf_to_mkdoc-0.1.
|
|
40
|
-
drf_to_mkdoc-0.1.
|
|
41
|
-
drf_to_mkdoc-0.1.
|
|
42
|
-
drf_to_mkdoc-0.1.
|
|
43
|
-
drf_to_mkdoc-0.1.
|
|
46
|
+
drf_to_mkdoc-0.1.9.dist-info/licenses/LICENSE,sha256=3n9_ckIREsH8ogCxWW6dFsw_WfGcluG2mHcgl9i_UU0,1068
|
|
47
|
+
drf_to_mkdoc-0.1.9.dist-info/METADATA,sha256=FgkNktIYpIuto47SGr2cvo_DdxMs15HGG6d8uaZAc7w,7037
|
|
48
|
+
drf_to_mkdoc-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
49
|
+
drf_to_mkdoc-0.1.9.dist-info/top_level.txt,sha256=ZzJecR6j_tvLZiubUBEgawHflozC4DQy9ooNf1yDJ3Q,13
|
|
50
|
+
drf_to_mkdoc-0.1.9.dist-info/RECORD,,
|