drf-to-mkdoc 0.2.1__py3-none-any.whl → 0.2.3__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/conf/defaults.py +1 -0
- drf_to_mkdoc/conf/settings.py +0 -2
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/form-manager.js +172 -0
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/main.js +22 -0
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/modal.js +79 -0
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/request-executor.js +111 -0
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/suggestions.js +216 -0
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/tabs.js +34 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/buttons.css +71 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/fab.css +47 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/form.css +124 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/key-value.css +161 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/main.css +57 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/modal.css +112 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/response.css +158 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/tabs.css +62 -0
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/variables.css +38 -0
- drf_to_mkdoc/templates/endpoints/detail/base.html +35 -0
- drf_to_mkdoc/templates/endpoints/detail/path_parameters.html +8 -0
- drf_to_mkdoc/templates/endpoints/detail/query_parameters.html +36 -0
- drf_to_mkdoc/templates/endpoints/detail/request_body.html +10 -0
- drf_to_mkdoc/templates/endpoints/detail/responses.html +18 -0
- drf_to_mkdoc/templates/endpoints/list/base.html +23 -0
- drf_to_mkdoc/templates/endpoints/list/endpoint_card.html +18 -0
- drf_to_mkdoc/templates/endpoints/list/filter_section.html +16 -0
- drf_to_mkdoc/templates/endpoints/list/filters/app.html +8 -0
- drf_to_mkdoc/templates/endpoints/list/filters/method.html +12 -0
- drf_to_mkdoc/templates/endpoints/list/filters/path.html +5 -0
- drf_to_mkdoc/templates/endpoints/list/filters/search.html +9 -0
- drf_to_mkdoc/templates/model_detail/base.html +34 -0
- drf_to_mkdoc/templates/model_detail/choices.html +12 -0
- drf_to_mkdoc/templates/model_detail/fields.html +11 -0
- drf_to_mkdoc/templates/model_detail/meta.html +6 -0
- drf_to_mkdoc/templates/model_detail/methods.html +9 -0
- drf_to_mkdoc/templates/model_detail/relationships.html +8 -0
- drf_to_mkdoc/templates/models_index.html +24 -0
- drf_to_mkdoc/templates/try-out/fab.html +4 -0
- drf_to_mkdoc/templates/try-out/form.html +113 -0
- drf_to_mkdoc/templates/try-out/main.html +4 -0
- drf_to_mkdoc/templates/try-out/modal.html +14 -0
- drf_to_mkdoc/templates/try-out/response-modal.html +20 -0
- drf_to_mkdoc/templatetags/custom_filters.py +148 -0
- drf_to_mkdoc/utils/commons/schema_utils.py +5 -14
- drf_to_mkdoc/utils/endpoint_detail_generator.py +201 -171
- drf_to_mkdoc/utils/endpoint_list_generator.py +58 -193
- drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +0 -15
- drf_to_mkdoc/utils/model_detail_generator.py +22 -202
- drf_to_mkdoc/utils/model_list_generator.py +26 -44
- drf_to_mkdoc/utils/schema.py +1 -1
- {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/METADATA +1 -1
- drf_to_mkdoc-0.2.3.dist-info/RECORD +103 -0
- drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +0 -879
- drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +0 -728
- drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
- drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -72
- drf_to_mkdoc-0.2.1.dist-info/RECORD +0 -67
- {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/WHEEL +0 -0
- {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
+
from django.template.loader import render_to_string
|
|
4
5
|
from django.templatetags.static import static
|
|
5
6
|
|
|
6
7
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
@@ -9,211 +10,75 @@ from drf_to_mkdoc.utils.commons.operation_utils import extract_viewset_from_oper
|
|
|
9
10
|
|
|
10
11
|
class EndpointsIndexGenerator:
|
|
11
12
|
def __init__(self, active_filters: list[str] | None = None):
|
|
12
|
-
self.active_filters =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
"""
|
|
13
|
+
self.active_filters = active_filters or [
|
|
14
|
+
"method",
|
|
15
|
+
"path",
|
|
16
|
+
"app",
|
|
17
|
+
"models",
|
|
18
|
+
"auth",
|
|
19
|
+
"roles",
|
|
20
|
+
"content_type",
|
|
21
|
+
"params",
|
|
22
|
+
"schema",
|
|
23
|
+
"pagination",
|
|
24
|
+
"ordering",
|
|
25
|
+
"search",
|
|
26
|
+
"tags",
|
|
27
|
+
]
|
|
165
28
|
|
|
166
29
|
def create_endpoints_index(
|
|
167
30
|
self, endpoints_by_app: dict[str, list[dict[str, Any]]], docs_dir: Path
|
|
168
31
|
) -> None:
|
|
32
|
+
prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
|
|
169
33
|
stylesheets = [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
34
|
+
static(prefix_path + path)
|
|
35
|
+
for path in [
|
|
36
|
+
"stylesheets/endpoints/variables.css",
|
|
37
|
+
"stylesheets/endpoints/base.css",
|
|
38
|
+
"stylesheets/endpoints/theme-toggle.css",
|
|
39
|
+
"stylesheets/endpoints/filter-section.css",
|
|
40
|
+
"stylesheets/endpoints/layout.css",
|
|
41
|
+
"stylesheets/endpoints/endpoints-grid.css",
|
|
42
|
+
"stylesheets/endpoints/badges.css",
|
|
43
|
+
"stylesheets/endpoints/endpoint-content.css",
|
|
44
|
+
"stylesheets/endpoints/tags.css",
|
|
45
|
+
"stylesheets/endpoints/sections.css",
|
|
46
|
+
"stylesheets/endpoints/stats.css",
|
|
47
|
+
"stylesheets/endpoints/loading.css",
|
|
48
|
+
"stylesheets/endpoints/animations.css",
|
|
49
|
+
"stylesheets/endpoints/responsive.css",
|
|
50
|
+
"stylesheets/endpoints/accessibility.css",
|
|
51
|
+
"stylesheets/endpoints/fixes.css",
|
|
52
|
+
]
|
|
186
53
|
]
|
|
187
54
|
|
|
188
55
|
scripts = [
|
|
189
|
-
"javascripts/endpoints-filter.js",
|
|
56
|
+
static(prefix_path + "javascripts/endpoints-filter.js"),
|
|
190
57
|
]
|
|
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
58
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
59
|
+
# Process endpoints to add view_class
|
|
60
|
+
processed_endpoints = {}
|
|
61
|
+
for app_name, app_endpoints in endpoints_by_app.items():
|
|
62
|
+
processed_endpoints[app_name] = []
|
|
63
|
+
for endpoint in app_endpoints:
|
|
64
|
+
processed_endpoint = endpoint.copy()
|
|
65
|
+
processed_endpoint["view_class"] = extract_viewset_from_operation_id(
|
|
66
|
+
endpoint["operation_id"]
|
|
67
|
+
)
|
|
68
|
+
processed_endpoint["link_url"] = (
|
|
69
|
+
f"{app_name}/{processed_endpoint['viewset'].lower()}/{processed_endpoint['filename'].replace('.md', '/index.html')}"
|
|
70
|
+
)
|
|
71
|
+
processed_endpoints[app_name].append(processed_endpoint)
|
|
72
|
+
|
|
73
|
+
context = {
|
|
74
|
+
"stylesheets": stylesheets,
|
|
75
|
+
"scripts": scripts,
|
|
76
|
+
"endpoints_by_app": processed_endpoints,
|
|
77
|
+
"active_filters": self.active_filters,
|
|
78
|
+
}
|
|
208
79
|
|
|
209
|
-
|
|
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"
|
|
80
|
+
content = render_to_string("endpoints/list/base.html", context)
|
|
215
81
|
|
|
216
|
-
content += "</div>\n"
|
|
217
82
|
output_path = docs_dir / "endpoints" / "index.md"
|
|
218
83
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
219
84
|
with Path(output_path).open("w", encoding="utf-8") as f:
|
|
@@ -11,7 +11,6 @@ def extract_query_parameters_from_view(operation_id: str) -> dict[str, Any]:
|
|
|
11
11
|
"search_fields": [],
|
|
12
12
|
"filter_fields": [],
|
|
13
13
|
"ordering_fields": [],
|
|
14
|
-
"filter_backends": [],
|
|
15
14
|
"pagination_fields": [],
|
|
16
15
|
}
|
|
17
16
|
|
|
@@ -19,7 +18,6 @@ def extract_query_parameters_from_view(operation_id: str) -> dict[str, Any]:
|
|
|
19
18
|
"search_fields": extract_query_parameters_from_view_search_fields(view_class),
|
|
20
19
|
"filter_fields": extract_query_parameters_from_view_filter_fields(view_class),
|
|
21
20
|
"ordering_fields": extract_query_parameters_from_view_ordering_fields(view_class),
|
|
22
|
-
"filter_backends": extract_query_parameters_from_view_filter_backends(view_class),
|
|
23
21
|
"pagination_fields": extract_query_parameters_from_view_pagination_fields(view_class),
|
|
24
22
|
}
|
|
25
23
|
|
|
@@ -62,19 +60,6 @@ def extract_query_parameters_from_view_ordering_fields(view_class: Any) -> list[
|
|
|
62
60
|
return ordering_fields
|
|
63
61
|
|
|
64
62
|
|
|
65
|
-
def extract_query_parameters_from_view_filter_backends(view_class: Any) -> list[str]:
|
|
66
|
-
"""Extract filter backends from a Django view class"""
|
|
67
|
-
if not view_class:
|
|
68
|
-
return []
|
|
69
|
-
|
|
70
|
-
filter_backends = []
|
|
71
|
-
if hasattr(view_class, "filter_backends") and view_class.filter_backends:
|
|
72
|
-
for backend in view_class.filter_backends:
|
|
73
|
-
filter_backends.append(getattr(backend, "__name__", str(backend)))
|
|
74
|
-
|
|
75
|
-
return filter_backends
|
|
76
|
-
|
|
77
|
-
|
|
78
63
|
def extract_query_parameters_from_view_pagination_fields(view_class: Any) -> list[str]:
|
|
79
64
|
"""Extract pagination fields from a Django view class"""
|
|
80
65
|
if not view_class:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
+
from django.template.loader import render_to_string
|
|
3
4
|
from django.templatetags.static import static
|
|
4
5
|
|
|
5
6
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
@@ -27,216 +28,35 @@ def generate_model_docs(models_data: dict[str, Any]) -> None:
|
|
|
27
28
|
write_file(file_path, content)
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
def render_column_fields_table(fields: dict[str, Any]) -> str:
|
|
31
|
-
"""Render the fields table for a model."""
|
|
32
|
-
content = "## Fields\n\n"
|
|
33
|
-
content += "| Field | Type | Description | Extra |\n"
|
|
34
|
-
content += "|-------|------|-------------|-------|\n"
|
|
35
|
-
|
|
36
|
-
for field_name, field_info in fields.items():
|
|
37
|
-
field_type = field_info.get("type", "Unknown")
|
|
38
|
-
verbose_name = field_info.get("verbose_name", field_name)
|
|
39
|
-
help_text = field_info.get("help_text", "")
|
|
40
|
-
|
|
41
|
-
display_name = field_name
|
|
42
|
-
if field_type in ["ForeignKey", "OneToOneField"]:
|
|
43
|
-
display_name = f"{field_name}_id"
|
|
44
|
-
|
|
45
|
-
extra_info = []
|
|
46
|
-
if field_info.get("null"):
|
|
47
|
-
extra_info.append("null=True")
|
|
48
|
-
if field_info.get("blank"):
|
|
49
|
-
extra_info.append("blank=True")
|
|
50
|
-
if field_info.get("unique"):
|
|
51
|
-
extra_info.append("unique=True")
|
|
52
|
-
if field_info.get("primary_key"):
|
|
53
|
-
extra_info.append("primary_key=True")
|
|
54
|
-
if field_info.get("default"):
|
|
55
|
-
extra_info.append(f"default={field_info['default']}")
|
|
56
|
-
|
|
57
|
-
field_specific = field_info.get("field_specific", {})
|
|
58
|
-
for key, value in field_specific.items():
|
|
59
|
-
if key not in ["related_name", "related_query_name", "to"]:
|
|
60
|
-
extra_info.append(f"{key}={value}")
|
|
61
|
-
|
|
62
|
-
extra_str = ", ".join(extra_info) if extra_info else ""
|
|
63
|
-
description_str = help_text or verbose_name
|
|
64
|
-
|
|
65
|
-
content += f"| `{display_name}` | {field_type} | {description_str} | {extra_str} |\n"
|
|
66
|
-
|
|
67
|
-
return content
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def render_choices_tables(fields: dict[str, Any]) -> str:
|
|
71
|
-
"""Render choice tables for fields with choices."""
|
|
72
|
-
choice_tables = []
|
|
73
|
-
|
|
74
|
-
for field_name, field_info in fields.items():
|
|
75
|
-
choices = field_info.get("choices")
|
|
76
|
-
if choices:
|
|
77
|
-
table = f"### {field_name} Choices\n\n"
|
|
78
|
-
table += "| Label | Value |\n"
|
|
79
|
-
table += "|-------|--------|\n"
|
|
80
|
-
for choice in choices:
|
|
81
|
-
table += f"| {choice['display']} | `{choice['value']}` |\n"
|
|
82
|
-
table += "\n"
|
|
83
|
-
choice_tables.append(table)
|
|
84
|
-
|
|
85
|
-
if choice_tables:
|
|
86
|
-
return "## Choices\n\n" + "\n".join(choice_tables)
|
|
87
|
-
return ""
|
|
88
|
-
|
|
89
|
-
|
|
90
31
|
def create_model_page(model_info: dict[str, Any]) -> str:
|
|
91
32
|
"""Create a model documentation page from model info"""
|
|
92
33
|
name = model_info.get("name", "Unknown")
|
|
93
34
|
app_label = model_info.get("app_label", "unknown")
|
|
94
35
|
table_name = model_info.get("table_name", "")
|
|
95
36
|
description = get_model_description(name)
|
|
37
|
+
column_fields = model_info.get("column_fields", {})
|
|
96
38
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
content += _add_relationships_section(model_info)
|
|
100
|
-
content += _add_methods_section(model_info)
|
|
101
|
-
content += _add_meta_options_section(model_info)
|
|
102
|
-
|
|
103
|
-
return content
|
|
104
|
-
|
|
39
|
+
# Check if any fields have choices
|
|
40
|
+
has_choices = any(field_info.get("choices") for field_info in column_fields.values())
|
|
105
41
|
|
|
106
|
-
def _create_model_header(name: str, app_label: str, table_name: str, description: str) -> str:
|
|
107
|
-
"""Create the header section of the model documentation."""
|
|
108
42
|
stylesheets = [
|
|
109
|
-
"stylesheets/models/variables.css",
|
|
110
|
-
"stylesheets/models/base.css",
|
|
111
|
-
"stylesheets/models/model-tables.css",
|
|
112
|
-
"stylesheets/models/responsive.css",
|
|
43
|
+
static(f"{drf_to_mkdoc_settings.PROJECT_NAME}/stylesheets/models/variables.css"),
|
|
44
|
+
static(f"{drf_to_mkdoc_settings.PROJECT_NAME}/stylesheets/models/base.css"),
|
|
45
|
+
static(f"{drf_to_mkdoc_settings.PROJECT_NAME}/stylesheets/models/model-tables.css"),
|
|
46
|
+
static(f"{drf_to_mkdoc_settings.PROJECT_NAME}/stylesheets/models/responsive.css"),
|
|
113
47
|
]
|
|
114
|
-
prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
|
|
115
|
-
css_links = "\n".join(
|
|
116
|
-
f'<link rel="stylesheet" href="{static(prefix_path + path)}">' for path in stylesheets
|
|
117
|
-
)
|
|
118
|
-
return f"""# {name}
|
|
119
|
-
|
|
120
|
-
<!-- inject CSS directly -->
|
|
121
|
-
{css_links}
|
|
122
|
-
|
|
123
|
-
**App:** {app_label}
|
|
124
|
-
**Table:** `{table_name}`
|
|
125
|
-
|
|
126
|
-
## Description
|
|
127
|
-
|
|
128
|
-
{description}
|
|
129
|
-
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _add_fields_section(model_info: dict[str, Any]) -> str:
|
|
134
|
-
"""Add the fields section to the model documentation."""
|
|
135
|
-
column_fields = model_info.get("column_fields", {})
|
|
136
|
-
if not column_fields:
|
|
137
|
-
return ""
|
|
138
|
-
|
|
139
|
-
content = ""
|
|
140
|
-
|
|
141
|
-
column_fields_content = render_column_fields_table(column_fields)
|
|
142
|
-
if column_fields_content:
|
|
143
|
-
content += column_fields_content
|
|
144
|
-
content += "\n"
|
|
145
|
-
|
|
146
|
-
choices_content = render_choices_tables(column_fields)
|
|
147
|
-
if choices_content:
|
|
148
|
-
content += choices_content
|
|
149
|
-
content += "\n"
|
|
150
|
-
|
|
151
|
-
return content
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _add_relationships_section(model_info: dict[str, Any]) -> str:
|
|
155
|
-
"""Add the relationships section to the model documentation."""
|
|
156
|
-
relationship_fields = model_info.get("relationships", {})
|
|
157
|
-
if not relationship_fields:
|
|
158
|
-
return ""
|
|
159
|
-
|
|
160
|
-
content = "## Relationships\n\n"
|
|
161
|
-
content += "| Field | Type | Related Model |\n"
|
|
162
|
-
content += "|-------|------|---------------|\n"
|
|
163
|
-
|
|
164
|
-
content += _render_relationship_fields(relationship_fields)
|
|
165
|
-
content += _render_relationships_from_section(relationship_fields)
|
|
166
|
-
content += "\n"
|
|
167
|
-
|
|
168
|
-
return content
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def _render_relationship_fields(relationship_fields: dict[str, Any]) -> str:
|
|
172
|
-
"""Render relationship fields from the fields section."""
|
|
173
|
-
content = ""
|
|
174
|
-
for field_name, field_info in relationship_fields.items():
|
|
175
|
-
field_type = field_info.get("type", "Unknown")
|
|
176
|
-
field_specific = field_info.get("field_specific", {})
|
|
177
|
-
to_model = field_specific.get("to", "")
|
|
178
|
-
|
|
179
|
-
if to_model:
|
|
180
|
-
model_link = _create_model_link(to_model)
|
|
181
|
-
content += f"| `{field_name}` | {field_type} | {model_link}|\n"
|
|
182
|
-
|
|
183
|
-
return content
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def _render_relationships_from_section(relationships: dict[str, Any]) -> str:
|
|
187
|
-
"""Render relationships from the relationships section."""
|
|
188
|
-
content = ""
|
|
189
|
-
for rel_name, rel_info in relationships.items():
|
|
190
|
-
rel_type = rel_info.get("type", "Unknown")
|
|
191
|
-
|
|
192
|
-
app_label = rel_info["app_label"]
|
|
193
|
-
table_name = rel_info["table_name"]
|
|
194
|
-
verbose_name = rel_info["verbose_name"]
|
|
195
|
-
|
|
196
|
-
model_link = f"[{verbose_name.capitalize()}](../../{app_label}/{table_name}/)"
|
|
197
|
-
|
|
198
|
-
content += f"| `{rel_name}` | {rel_type} | {model_link} | \n"
|
|
199
|
-
|
|
200
|
-
return content
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def _create_model_link(to_model: str) -> str:
|
|
204
|
-
"""Create a link to a related model."""
|
|
205
|
-
if "." in to_model:
|
|
206
|
-
related_app, related_model = to_model.split(".", 1)
|
|
207
|
-
return f"[{related_model}](../{related_app}/{related_model.lower()}/)"
|
|
208
|
-
return f"[{to_model}]({to_model.lower()}/)"
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def _add_methods_section(model_info: dict[str, Any]) -> str:
|
|
212
|
-
"""Add the methods section to the model documentation."""
|
|
213
|
-
methods = model_info.get("methods", [])
|
|
214
|
-
if not methods:
|
|
215
|
-
return ""
|
|
216
|
-
|
|
217
|
-
content = "## Methods\n\n"
|
|
218
|
-
for method in methods:
|
|
219
|
-
method_name = method.get("name", "")
|
|
220
|
-
docstring = method.get("docstring", "")
|
|
221
|
-
|
|
222
|
-
content += f"### `{method_name}()`\n\n"
|
|
223
|
-
if docstring:
|
|
224
|
-
content += f"{docstring}\n\n"
|
|
225
|
-
else:
|
|
226
|
-
content += "No documentation available.\n\n"
|
|
227
|
-
|
|
228
|
-
return content
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def _add_meta_options_section(model_info: dict[str, Any]) -> str:
|
|
232
|
-
"""Add the meta options section to the model documentation."""
|
|
233
|
-
meta_options = model_info.get("meta_options", {})
|
|
234
|
-
if not meta_options:
|
|
235
|
-
return ""
|
|
236
|
-
|
|
237
|
-
content = "## Meta Options\n\n"
|
|
238
|
-
for option, value in meta_options.items():
|
|
239
|
-
content += f"- **{option}:** {value}\n"
|
|
240
|
-
content += "\n"
|
|
241
48
|
|
|
242
|
-
|
|
49
|
+
context = {
|
|
50
|
+
"name": name,
|
|
51
|
+
"app_label": app_label,
|
|
52
|
+
"table_name": table_name,
|
|
53
|
+
"description": description,
|
|
54
|
+
"stylesheets": stylesheets,
|
|
55
|
+
"fields": column_fields, # Changed from column_fields to fields for template consistency
|
|
56
|
+
"has_choices": has_choices, # Added choices flag
|
|
57
|
+
"relationships": model_info.get("relationships", {}),
|
|
58
|
+
"methods": model_info.get("methods", []),
|
|
59
|
+
"meta_options": model_info.get("meta_options", {}),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return render_to_string("model_detail/base.html", context)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
from html import escape
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
from typing import Any
|
|
4
|
-
from urllib.parse import quote
|
|
5
3
|
|
|
4
|
+
from django.template.loader import render_to_string
|
|
6
5
|
from django.templatetags.static import static
|
|
7
6
|
|
|
8
7
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
@@ -12,44 +11,22 @@ from drf_to_mkdoc.utils.commons.model_utils import get_app_descriptions
|
|
|
12
11
|
def create_models_index(models_data: dict[str, Any], docs_dir: Path) -> None:
|
|
13
12
|
"""Create the main models index page that lists all models organized by app."""
|
|
14
13
|
stylesheets = [
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
static(f"{drf_to_mkdoc_settings.PROJECT_NAME}/{path}")
|
|
15
|
+
for path in [
|
|
16
|
+
"stylesheets/models/variables.css",
|
|
17
|
+
"stylesheets/models/base.css",
|
|
18
|
+
"stylesheets/models/model-cards.css",
|
|
19
|
+
"stylesheets/models/responsive.css",
|
|
20
|
+
"stylesheets/models/animations.css",
|
|
21
|
+
]
|
|
20
22
|
]
|
|
21
|
-
prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
|
|
22
|
-
css_links = "\n".join(
|
|
23
|
-
f'<link rel="stylesheet" href="{static(prefix_path + path)}">' for path in stylesheets
|
|
24
|
-
)
|
|
25
|
-
content = f"""# Django Models
|
|
26
|
-
|
|
27
|
-
This section contains documentation for all Django models in the system, organized by Django application.
|
|
28
|
-
|
|
29
|
-
<!-- inject CSS directly -->
|
|
30
|
-
{css_links}
|
|
31
|
-
|
|
32
|
-
<div class="models-container">
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
app_descriptions = get_app_descriptions()
|
|
36
23
|
|
|
24
|
+
sorted_models = []
|
|
37
25
|
for app_name, models in sorted(models_data.items()):
|
|
38
|
-
app_desc = escape(
|
|
39
|
-
app_descriptions.get(
|
|
40
|
-
app_name, f"{app_name.replace('_', ' ').title()} application models"
|
|
41
|
-
)
|
|
42
|
-
)
|
|
43
|
-
app_title = escape(app_name.replace("_", " ").title())
|
|
44
|
-
content += f'<div class="app-header">{app_title} App</div>\n'
|
|
45
|
-
content += f'<div class="app-description">{app_desc}</div>\n\n'
|
|
46
|
-
|
|
47
|
-
content += '<div class="model-cards">\n'
|
|
48
|
-
|
|
49
26
|
model_names = sorted(
|
|
50
27
|
[
|
|
51
28
|
(
|
|
52
|
-
str(mi.get("verbose_name") or mk),
|
|
29
|
+
str(mi.get("verbose_name") or mk).capitalize(),
|
|
53
30
|
str(mi.get("table_name") or mk),
|
|
54
31
|
)
|
|
55
32
|
for mk, mi in models.items()
|
|
@@ -57,18 +34,23 @@ This section contains documentation for all Django models in the system, organiz
|
|
|
57
34
|
],
|
|
58
35
|
key=lambda x: x[0].casefold(),
|
|
59
36
|
)
|
|
60
|
-
|
|
61
|
-
content += f"""
|
|
62
|
-
<a href="{quote(app_name, safe="")}/{quote(table_name, safe="")}/"
|
|
63
|
-
class="model-card">{escape(verbose_name)}</a>\n
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
content += "</div>\n\n"
|
|
67
|
-
|
|
68
|
-
content += """</div>
|
|
37
|
+
sorted_models.append((app_name, model_names))
|
|
69
38
|
|
|
39
|
+
app_descriptions = get_app_descriptions()
|
|
40
|
+
for app_name, _ in sorted_models:
|
|
41
|
+
if app_name not in app_descriptions:
|
|
42
|
+
app_descriptions[app_name] = (
|
|
43
|
+
f"{app_name.replace('_', ' ').title()} application models"
|
|
44
|
+
)
|
|
70
45
|
|
|
71
|
-
|
|
46
|
+
content = render_to_string(
|
|
47
|
+
"models_index.html",
|
|
48
|
+
{
|
|
49
|
+
"stylesheets": stylesheets,
|
|
50
|
+
"sorted_models": sorted_models,
|
|
51
|
+
"app_descriptions": app_descriptions,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
72
54
|
|
|
73
55
|
models_index_path = docs_dir / "models" / "index.md"
|
|
74
56
|
models_index_path.parent.mkdir(parents=True, exist_ok=True)
|
drf_to_mkdoc/utils/schema.py
CHANGED
|
@@ -58,7 +58,7 @@ class ViewMetadataExtractor:
|
|
|
58
58
|
|
|
59
59
|
try:
|
|
60
60
|
serializer_cls = self.view_instance.get_serializer_class()
|
|
61
|
-
except
|
|
61
|
+
except Exception as e:
|
|
62
62
|
logger.debug(f"Failed to get serializer from view instance: {e}")
|
|
63
63
|
return None
|
|
64
64
|
else:
|