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
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{% load custom_filters %}
|
|
2
|
+
|
|
3
|
+
<div class="try-out-form" data-method="{{ method }}">
|
|
4
|
+
<!-- Base URL Section -->
|
|
5
|
+
<div class="base-url-section">
|
|
6
|
+
<label for="baseUrl">Request URL</label>
|
|
7
|
+
<div class="url-display">
|
|
8
|
+
<input type="text" id="baseUrl" class="base-url-input" value="http://localhost:8000" placeholder="https://api.example.com">
|
|
9
|
+
<span class="path-display">{{ path }}</span>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<!-- Tabs -->
|
|
14
|
+
<div class="tabs">
|
|
15
|
+
<button class="tab active" data-tab="parameters">Parameters</button>
|
|
16
|
+
<button class="tab" data-tab="headers">Headers</button>
|
|
17
|
+
{% if method|upper in "POST,PUT,PATCH" %}
|
|
18
|
+
<button class="tab" data-tab="body">Request Body</button>
|
|
19
|
+
{% endif %}
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- Parameters Tab -->
|
|
23
|
+
<div class="tab-content active" id="parametersTab">
|
|
24
|
+
{% if path_params %}
|
|
25
|
+
<div class="form-group">
|
|
26
|
+
<label class="form-label">Path Parameters</label>
|
|
27
|
+
<div class="kv-container" id="pathParams">
|
|
28
|
+
{% for param in path_params %}
|
|
29
|
+
<div class="kv-item">
|
|
30
|
+
<label class="param-label{% if param.required %} required{% endif %}">{{ param.name }}</label>
|
|
31
|
+
<input type="text"
|
|
32
|
+
placeholder="Enter {{ param.name }} value"
|
|
33
|
+
data-param="{{ param.name }}"
|
|
34
|
+
{% if param.required %}required{% endif %}>
|
|
35
|
+
<div class="error-message"></div>
|
|
36
|
+
</div>
|
|
37
|
+
{% endfor %}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{% endif %}
|
|
41
|
+
|
|
42
|
+
<div class="form-group">
|
|
43
|
+
<label class="form-label">Query Parameters</label>
|
|
44
|
+
<div class="kv-container" id="queryParams">
|
|
45
|
+
<div class="kv-item">
|
|
46
|
+
<input type="text" placeholder="Parameter name">
|
|
47
|
+
<input type="text" placeholder="Parameter value">
|
|
48
|
+
<button class="remove-btn" onclick="TryOutSidebar.removeKvItem(this)">✕</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<button class="add-btn" onclick="TryOutSidebar.addQueryParam()">
|
|
52
|
+
<span>+</span> Add Parameter
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Headers Tab -->
|
|
58
|
+
<div class="tab-content" id="headersTab">
|
|
59
|
+
<div class="form-group">
|
|
60
|
+
<label class="form-label">Request Headers</label>
|
|
61
|
+
<div class="kv-container" id="requestHeaders">
|
|
62
|
+
<div class="kv-item">
|
|
63
|
+
<input type="text" value="Content-Type">
|
|
64
|
+
<input type="text" value="application/json">
|
|
65
|
+
<button class="remove-btn" onclick="TryOutSidebar.removeKvItem(this)">✕</button>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="kv-item">
|
|
68
|
+
<input type="text" value="Authorization">
|
|
69
|
+
<input type="text" placeholder="Bearer your-token">
|
|
70
|
+
<button class="remove-btn" onclick="TryOutSidebar.removeKvItem(this)">✕</button>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<button class="add-btn" onclick="TryOutSidebar.addHeader()">
|
|
74
|
+
<span>+</span> Add Header
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Request Body Tab -->
|
|
80
|
+
{% if method|upper in "POST,PUT,PATCH" %}
|
|
81
|
+
<div class="tab-content" id="bodyTab">
|
|
82
|
+
<div class="form-group">
|
|
83
|
+
<label class="form-label">Request Body</label>
|
|
84
|
+
<textarea id="requestBody"
|
|
85
|
+
class="form-input form-textarea"
|
|
86
|
+
placeholder="Enter JSON request body..."
|
|
87
|
+
rows="8">{% if request_example %}{{ request_example|extract_json_from_markdown }}{% endif %}</textarea>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{% endif %}
|
|
91
|
+
|
|
92
|
+
<!-- Execute Button -->
|
|
93
|
+
<button class="execute-btn" id="executeBtn" onclick="executeRequest()">
|
|
94
|
+
<span>▶</span> Execute Request
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<script>
|
|
99
|
+
// Pass query parameters data to JavaScript
|
|
100
|
+
window.queryParametersData = {
|
|
101
|
+
{% if query_parameters %}
|
|
102
|
+
filter_fields: {{ query_parameters.filter_fields|default:"[]"|safe }},
|
|
103
|
+
search_fields: {{ query_parameters.search_fields|default:"[]"|safe }},
|
|
104
|
+
ordering_fields: {{ query_parameters.ordering_fields|default:"[]"|safe }},
|
|
105
|
+
pagination_fields: {{ query_parameters.pagination_fields|default:"[]"|safe }}
|
|
106
|
+
{% else %}
|
|
107
|
+
filter_fields: [],
|
|
108
|
+
search_fields: [],
|
|
109
|
+
ordering_fields: [],
|
|
110
|
+
pagination_fields: []
|
|
111
|
+
{% endif %}
|
|
112
|
+
};
|
|
113
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- Try Out Modal -->
|
|
2
|
+
<div id="tryOutModal" class="try-out-modal" role="dialog" aria-modal="true" aria-label="Try It Out">
|
|
3
|
+
<div class="modal-overlay" onclick="TryOutSidebar.closeTryOut()"></div>
|
|
4
|
+
<div class="modal-content">
|
|
5
|
+
<div class="modal-header">
|
|
6
|
+
<span>🚀</span>
|
|
7
|
+
<h3>Try It Out</h3>
|
|
8
|
+
<button class="modal-close" aria-label="Close" onclick="TryOutSidebar.closeTryOut()">✕</button>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="modal-body">
|
|
11
|
+
{% include 'try-out/form.html' %}
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- Response Modal -->
|
|
2
|
+
<div id="responseModal" class="response-modal" role="dialog" aria-modal="true" aria-label="API Response">
|
|
3
|
+
<div class="modal-overlay" onclick="TryOutSidebar.closeResponseModal()"></div>
|
|
4
|
+
<div class="modal-content">
|
|
5
|
+
<div class="modal-header">
|
|
6
|
+
<h3>API Response</h3>
|
|
7
|
+
<button class="modal-close" aria-label="Close" onclick="TryOutSidebar.closeResponseModal()">✕</button>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="modal-body">
|
|
10
|
+
<div class="response-header">
|
|
11
|
+
<div class="response-info">
|
|
12
|
+
<span>Status:</span>
|
|
13
|
+
<span class="status-badge" id="modalStatusBadge"></span>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="response-info" id="responseInfo"></div>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="response-body" id="modalResponseBody"></div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from django import template
|
|
6
|
+
from django.templatetags.static import static as django_static
|
|
7
|
+
from django.utils.safestring import mark_safe
|
|
8
|
+
|
|
9
|
+
from drf_to_mkdoc.utils.commons.operation_utils import (
|
|
10
|
+
format_method_badge as format_method_badge_util,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
register = template.Library()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@register.filter
|
|
17
|
+
def is_foreign_key(field_type):
|
|
18
|
+
return field_type in ["ForeignKey", "OneToOneField"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@register.simple_tag
|
|
22
|
+
def static_with_prefix(path, prefix=""):
|
|
23
|
+
"""Add prefix to static path"""
|
|
24
|
+
return django_static(prefix + path)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@register.filter
|
|
28
|
+
def format_method_badge(method):
|
|
29
|
+
"""Format HTTP method as badge"""
|
|
30
|
+
return format_method_badge_util(method)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@register.filter
|
|
34
|
+
def get_display_name(field_name, field_type):
|
|
35
|
+
if field_type in ["ForeignKey", "OneToOneField"]:
|
|
36
|
+
return f"{field_name}_id"
|
|
37
|
+
return field_name
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@register.filter
|
|
41
|
+
def split(value, delimiter=","):
|
|
42
|
+
return value.split(delimiter)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@register.filter
|
|
46
|
+
def cut(value, arg):
|
|
47
|
+
return value.split(arg)[1] if "." in value else value
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@register.filter
|
|
51
|
+
def get_item(dictionary, key):
|
|
52
|
+
return dictionary.get(key, "")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@register.filter
|
|
56
|
+
def format_field_type(field_info):
|
|
57
|
+
field_type = field_info.get("type", "")
|
|
58
|
+
if field_type == "ForeignKey":
|
|
59
|
+
return f"ForeignKey | {field_info.get('field_specific', {}).get('to', '')}"
|
|
60
|
+
if field_type == "CharField":
|
|
61
|
+
max_length = field_info.get("max_length", "")
|
|
62
|
+
return f"CharField | {field_info.get('verbose_name', '')} | max_length={max_length}"
|
|
63
|
+
return field_type
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@register.filter
|
|
67
|
+
def format_field_extra(field_info):
|
|
68
|
+
extras = []
|
|
69
|
+
if field_info.get("null"):
|
|
70
|
+
extras.append("null=True")
|
|
71
|
+
if field_info.get("blank"):
|
|
72
|
+
extras.append("blank=True")
|
|
73
|
+
if field_info.get("unique"):
|
|
74
|
+
extras.append("unique=True")
|
|
75
|
+
if field_info.get("primary_key"):
|
|
76
|
+
extras.append("primary_key=True")
|
|
77
|
+
if field_info.get("max_length"):
|
|
78
|
+
extras.append(f"max_length={field_info['max_length']}")
|
|
79
|
+
return ", ".join(extras)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@register.filter
|
|
83
|
+
def yesno(value, arg=None):
|
|
84
|
+
"""
|
|
85
|
+
Given a string mapping values for true, false and (optionally) None,
|
|
86
|
+
return one of those strings according to the value.
|
|
87
|
+
"""
|
|
88
|
+
if arg is None:
|
|
89
|
+
arg = "yes,no,maybe"
|
|
90
|
+
bits = arg.split(",")
|
|
91
|
+
if len(bits) < 2:
|
|
92
|
+
return value # Invalid arg.
|
|
93
|
+
try:
|
|
94
|
+
yes, no, maybe = bits
|
|
95
|
+
except ValueError:
|
|
96
|
+
yes, no, maybe = bits[0], bits[1], bits[1]
|
|
97
|
+
|
|
98
|
+
if value is None:
|
|
99
|
+
return maybe
|
|
100
|
+
if value:
|
|
101
|
+
return yes
|
|
102
|
+
return no
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@register.filter
|
|
106
|
+
def format_json(value):
|
|
107
|
+
if isinstance(value, str):
|
|
108
|
+
value = html.unescape(value)
|
|
109
|
+
try:
|
|
110
|
+
parsed = json.loads(value)
|
|
111
|
+
value = json.dumps(parsed, indent=2)
|
|
112
|
+
except (json.JSONDecodeError, TypeError):
|
|
113
|
+
pass
|
|
114
|
+
elif isinstance(value, dict | list):
|
|
115
|
+
value = json.dumps(value, indent=2)
|
|
116
|
+
|
|
117
|
+
return mark_safe(f"```json\n{value}\n```") # noqa: S308
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@register.filter
|
|
121
|
+
def extract_json_from_markdown(value):
|
|
122
|
+
"""Extract JSON content from markdown code blocks"""
|
|
123
|
+
if not isinstance(value, str):
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
# Look for ```json code blocks
|
|
127
|
+
|
|
128
|
+
json_pattern = r"```json\s*\n(.*?)\n```"
|
|
129
|
+
matches = re.findall(json_pattern, value, re.DOTALL)
|
|
130
|
+
|
|
131
|
+
if matches:
|
|
132
|
+
return matches[0].strip()
|
|
133
|
+
|
|
134
|
+
# Fallback: look for any code block
|
|
135
|
+
code_pattern = r"```\s*\n(.*?)\n```"
|
|
136
|
+
matches = re.findall(code_pattern, value, re.DOTALL)
|
|
137
|
+
|
|
138
|
+
if matches:
|
|
139
|
+
content = matches[0].strip()
|
|
140
|
+
# Try to validate if it's JSON
|
|
141
|
+
try:
|
|
142
|
+
json.loads(content)
|
|
143
|
+
except (json.JSONDecodeError, TypeError):
|
|
144
|
+
pass
|
|
145
|
+
else:
|
|
146
|
+
return content
|
|
147
|
+
|
|
148
|
+
return ""
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from functools import lru_cache
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
from typing import Any
|
|
4
6
|
|
|
5
|
-
import yaml
|
|
6
7
|
from drf_spectacular.generators import SchemaGenerator
|
|
7
8
|
|
|
8
9
|
from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
|
|
@@ -21,16 +22,6 @@ class QueryParamTypeError(Exception):
|
|
|
21
22
|
pass
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
def load_schema() -> dict[str, Any] | None:
|
|
25
|
-
"""Load the OpenAPI schema from doc-schema.yaml"""
|
|
26
|
-
schema_file = Path(drf_to_mkdoc_settings.CONFIG_DIR) / "doc-schema.yaml"
|
|
27
|
-
if not schema_file.exists():
|
|
28
|
-
return None
|
|
29
|
-
|
|
30
|
-
with schema_file.open(encoding="utf-8") as f:
|
|
31
|
-
return yaml.safe_load(f)
|
|
32
|
-
|
|
33
|
-
|
|
34
25
|
def get_custom_schema():
|
|
35
26
|
custom_schema_data = load_json_data(
|
|
36
27
|
drf_to_mkdoc_settings.CUSTOM_SCHEMA_FILE, raise_not_found=False
|
|
@@ -56,7 +47,6 @@ def get_custom_schema():
|
|
|
56
47
|
"search_fields",
|
|
57
48
|
"filter_fields",
|
|
58
49
|
"ordering_fields",
|
|
59
|
-
"filter_backends",
|
|
60
50
|
"pagination_fields",
|
|
61
51
|
}
|
|
62
52
|
):
|
|
@@ -153,16 +143,17 @@ def _apply_custom_overrides(
|
|
|
153
143
|
target_schema[key] = custom_value
|
|
154
144
|
|
|
155
145
|
|
|
146
|
+
@lru_cache(maxsize=1)
|
|
156
147
|
def get_schema():
|
|
157
148
|
base_schema = SchemaGenerator().get_schema(request=None, public=True)
|
|
158
149
|
custom_data = get_custom_schema()
|
|
159
150
|
if not custom_data:
|
|
160
|
-
return base_schema
|
|
151
|
+
return deepcopy(base_schema)
|
|
161
152
|
|
|
162
153
|
operation_map = _build_operation_map(base_schema)
|
|
163
154
|
_apply_custom_overrides(base_schema, operation_map, custom_data)
|
|
164
155
|
|
|
165
|
-
return base_schema
|
|
156
|
+
return deepcopy(base_schema)
|
|
166
157
|
|
|
167
158
|
|
|
168
159
|
class OperationExtractor:
|