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.

Files changed (59) hide show
  1. drf_to_mkdoc/conf/defaults.py +1 -0
  2. drf_to_mkdoc/conf/settings.py +0 -2
  3. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/form-manager.js +172 -0
  4. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/main.js +22 -0
  5. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/modal.js +79 -0
  6. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/request-executor.js +111 -0
  7. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/suggestions.js +216 -0
  8. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/tabs.js +34 -0
  9. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/buttons.css +71 -0
  10. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/fab.css +47 -0
  11. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/form.css +124 -0
  12. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/key-value.css +161 -0
  13. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/main.css +57 -0
  14. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/modal.css +112 -0
  15. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/response.css +158 -0
  16. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/tabs.css +62 -0
  17. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/variables.css +38 -0
  18. drf_to_mkdoc/templates/endpoints/detail/base.html +35 -0
  19. drf_to_mkdoc/templates/endpoints/detail/path_parameters.html +8 -0
  20. drf_to_mkdoc/templates/endpoints/detail/query_parameters.html +36 -0
  21. drf_to_mkdoc/templates/endpoints/detail/request_body.html +10 -0
  22. drf_to_mkdoc/templates/endpoints/detail/responses.html +18 -0
  23. drf_to_mkdoc/templates/endpoints/list/base.html +23 -0
  24. drf_to_mkdoc/templates/endpoints/list/endpoint_card.html +18 -0
  25. drf_to_mkdoc/templates/endpoints/list/filter_section.html +16 -0
  26. drf_to_mkdoc/templates/endpoints/list/filters/app.html +8 -0
  27. drf_to_mkdoc/templates/endpoints/list/filters/method.html +12 -0
  28. drf_to_mkdoc/templates/endpoints/list/filters/path.html +5 -0
  29. drf_to_mkdoc/templates/endpoints/list/filters/search.html +9 -0
  30. drf_to_mkdoc/templates/model_detail/base.html +34 -0
  31. drf_to_mkdoc/templates/model_detail/choices.html +12 -0
  32. drf_to_mkdoc/templates/model_detail/fields.html +11 -0
  33. drf_to_mkdoc/templates/model_detail/meta.html +6 -0
  34. drf_to_mkdoc/templates/model_detail/methods.html +9 -0
  35. drf_to_mkdoc/templates/model_detail/relationships.html +8 -0
  36. drf_to_mkdoc/templates/models_index.html +24 -0
  37. drf_to_mkdoc/templates/try-out/fab.html +4 -0
  38. drf_to_mkdoc/templates/try-out/form.html +113 -0
  39. drf_to_mkdoc/templates/try-out/main.html +4 -0
  40. drf_to_mkdoc/templates/try-out/modal.html +14 -0
  41. drf_to_mkdoc/templates/try-out/response-modal.html +20 -0
  42. drf_to_mkdoc/templatetags/custom_filters.py +148 -0
  43. drf_to_mkdoc/utils/commons/schema_utils.py +5 -14
  44. drf_to_mkdoc/utils/endpoint_detail_generator.py +201 -171
  45. drf_to_mkdoc/utils/endpoint_list_generator.py +58 -193
  46. drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +0 -15
  47. drf_to_mkdoc/utils/model_detail_generator.py +22 -202
  48. drf_to_mkdoc/utils/model_list_generator.py +26 -44
  49. drf_to_mkdoc/utils/schema.py +1 -1
  50. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/METADATA +1 -1
  51. drf_to_mkdoc-0.2.3.dist-info/RECORD +103 -0
  52. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +0 -879
  53. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +0 -728
  54. drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
  55. drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -72
  56. drf_to_mkdoc-0.2.1.dist-info/RECORD +0 -67
  57. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/WHEEL +0 -0
  58. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.3.dist-info}/licenses/LICENSE +0 -0
  59. {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,4 @@
1
+ <!-- Try Out Main Component -->
2
+ {% include 'try-out/fab.html' %}
3
+ {% include 'try-out/modal.html' %}
4
+ {% include 'try-out/response-modal.html' %}
@@ -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: