drf-to-mkdoc 0.2.2__py3-none-any.whl → 0.2.4__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 (47) hide show
  1. drf_to_mkdoc/conf/defaults.py +1 -0
  2. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/field-sections-loader.js +29 -0
  3. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/query-parameters-loader.js +16 -0
  4. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/field-extractor.js +200 -0
  5. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/form-manager.js +465 -0
  6. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/main.js +50 -0
  7. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/modal.js +359 -0
  8. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/query-parameters-extractor.js +94 -0
  9. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/request-executor.js +327 -0
  10. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/response-modal.js +173 -0
  11. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/suggestions.js +123 -0
  12. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out/tabs.js +77 -0
  13. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/badges.css +13 -5
  14. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/theme-toggle.css +297 -25
  15. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out/fab.css +204 -0
  16. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out/response.css +323 -0
  17. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out/variables.css +139 -0
  18. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/field-sections.css +136 -0
  19. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/buttons.css +71 -0
  20. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/fab.css +47 -0
  21. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/form.css +663 -0
  22. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/key-value.css +161 -0
  23. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/main.css +57 -0
  24. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/modal.css +334 -0
  25. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/response.css +618 -0
  26. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/tabs.css +114 -0
  27. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/try-out/variables.css +94 -0
  28. drf_to_mkdoc/templates/endpoints/detail/base.html +3 -1
  29. drf_to_mkdoc/templates/endpoints/detail/query_parameters.html +1 -8
  30. drf_to_mkdoc/templates/endpoints/detail/request_body.html +2 -0
  31. drf_to_mkdoc/templates/endpoints/detail/responses.html +4 -4
  32. drf_to_mkdoc/templates/try-out/fab.html +68 -0
  33. drf_to_mkdoc/templates/try-out/form.html +260 -0
  34. drf_to_mkdoc/templates/try-out/main.html +4 -0
  35. drf_to_mkdoc/templates/try-out/modal.html +82 -0
  36. drf_to_mkdoc/templates/try-out/response-modal.html +149 -0
  37. drf_to_mkdoc/templatetags/custom_filters.py +33 -1
  38. drf_to_mkdoc/utils/commons/schema_utils.py +5 -14
  39. drf_to_mkdoc/utils/endpoint_detail_generator.py +141 -21
  40. drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +0 -15
  41. {drf_to_mkdoc-0.2.2.dist-info → drf_to_mkdoc-0.2.4.dist-info}/METADATA +68 -9
  42. {drf_to_mkdoc-0.2.2.dist-info → drf_to_mkdoc-0.2.4.dist-info}/RECORD +45 -18
  43. drf_to_mkdoc/static/drf-to-mkdoc/javascripts/try-out-sidebar.js +0 -879
  44. drf_to_mkdoc/static/drf-to-mkdoc/stylesheets/endpoints/try-out-sidebar.css +0 -728
  45. {drf_to_mkdoc-0.2.2.dist-info → drf_to_mkdoc-0.2.4.dist-info}/WHEEL +0 -0
  46. {drf_to_mkdoc-0.2.2.dist-info → drf_to_mkdoc-0.2.4.dist-info}/licenses/LICENSE +0 -0
  47. {drf_to_mkdoc-0.2.2.dist-info → drf_to_mkdoc-0.2.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,149 @@
1
+ <!-- Response Modal -->
2
+ <div
3
+ id="responseModal"
4
+ class="response-modal"
5
+ role="dialog"
6
+ aria-modal="true"
7
+ aria-labelledby="responseTitle"
8
+ aria-describedby="responseDescription"
9
+ >
10
+ <div class="modal-overlay" onclick="TryOutSidebar.closeResponseModal()"></div>
11
+ <div class="modal-content">
12
+ <!-- Modal Header -->
13
+ <div class="modal-header">
14
+ <div class="header-content">
15
+ <h3 id="responseTitle">API Response</h3>
16
+ <span id="responseDescription" class="visually-hidden">Displays the API response details and content</span>
17
+ <div class="header-actions">
18
+ <button
19
+ class="action-btn"
20
+ onclick="copyResponse()"
21
+ aria-label="Copy response"
22
+ data-tooltip="Copy to clipboard"
23
+ >
24
+ <span class="icon">📋</span>
25
+ </button>
26
+ <button
27
+ class="action-btn"
28
+ onclick="downloadResponse()"
29
+ aria-label="Download response"
30
+ data-tooltip="Download as JSON"
31
+ >
32
+ <span class="icon">💾</span>
33
+ </button>
34
+ <button
35
+ class="modal-close"
36
+ aria-label="Close response modal"
37
+ onclick="TryOutSidebar.closeResponseModal()"
38
+ >
39
+ <span class="icon">✕</span>
40
+ </button>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <!-- Response Stats -->
46
+ <div class="response-stats">
47
+ <div class="stat-group">
48
+ <div class="stat-item">
49
+ <span class="stat-label">Status</span>
50
+ <span class="status-badge" id="modalStatusBadge"></span>
51
+ </div>
52
+ <div class="stat-item">
53
+ <span class="stat-label">Time</span>
54
+ <span class="stat-value" id="responseTime">0 ms</span>
55
+ </div>
56
+ <div class="stat-item">
57
+ <span class="stat-label">Size</span>
58
+ <span class="stat-value" id="responseSize">0 KB</span>
59
+ </div>
60
+ </div>
61
+ <div class="response-info" id="responseInfo"></div>
62
+ </div>
63
+
64
+ <!-- Response Content -->
65
+ <div class="modal-body">
66
+ <!-- Response Tabs -->
67
+ <div class="response-tabs" role="tablist">
68
+ <button
69
+ class="tab active"
70
+ role="tab"
71
+ aria-selected="true"
72
+ aria-controls="responseBody"
73
+ data-tab="body"
74
+ >
75
+ <span class="icon">📄</span>
76
+ Response
77
+ </button>
78
+ <button
79
+ class="tab"
80
+ role="tab"
81
+ aria-selected="false"
82
+ aria-controls="responseHeaders"
83
+ data-tab="headers"
84
+ >
85
+ <span class="icon">📋</span>
86
+ Headers
87
+ </button>
88
+ </div>
89
+
90
+ <!-- Response Body Tab -->
91
+ <div
92
+ id="responseBody"
93
+ class="tab-content active"
94
+ role="tabpanel"
95
+ >
96
+ <div class="response-toolbar">
97
+ <div class="toolbar-group">
98
+ <button
99
+ class="tool-btn"
100
+ onclick="formatResponse()"
101
+ data-tooltip="Format JSON"
102
+ >
103
+ <span class="icon">🔧</span>
104
+ </button>
105
+ <button
106
+ class="tool-btn"
107
+ onclick="collapseAll()"
108
+ data-tooltip="Collapse all"
109
+ >
110
+ <span class="icon">📦</span>
111
+ </button>
112
+ <button
113
+ class="tool-btn"
114
+ onclick="expandAll()"
115
+ data-tooltip="Expand all"
116
+ >
117
+ <span class="icon">📂</span>
118
+ </button>
119
+ </div>
120
+ </div>
121
+ <div class="response-viewer" role="region" aria-live="polite">
122
+ <div class="response-content" id="modalResponseBody" tabindex="0"></div>
123
+ </div>
124
+ </div>
125
+
126
+ <!-- Response Headers Tab -->
127
+ <div
128
+ id="responseHeaders"
129
+ class="tab-content"
130
+ role="tabpanel"
131
+ >
132
+ <div class="headers-list" id="responseHeadersList"></div>
133
+ </div>
134
+
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <script>
140
+ // Update event handlers to use the ResponseModalManager
141
+ document.addEventListener('DOMContentLoaded', function() {
142
+ // Update button click handlers
143
+ document.querySelector('button[onclick="copyResponse()"]').setAttribute('onclick', 'ResponseModalManager.copyResponse()');
144
+ document.querySelector('button[onclick="downloadResponse()"]').setAttribute('onclick', 'ResponseModalManager.downloadResponse()');
145
+ document.querySelector('button[onclick="formatResponse()"]').setAttribute('onclick', 'ResponseModalManager.formatResponse()');
146
+ document.querySelector('button[onclick="collapseAll()"]').setAttribute('onclick', 'ResponseModalManager.collapseAll()');
147
+ document.querySelector('button[onclick="expandAll()"]').setAttribute('onclick', 'ResponseModalManager.expandAll()');
148
+ });
149
+ </script>
@@ -1,5 +1,6 @@
1
1
  import html
2
2
  import json
3
+ import re
3
4
 
4
5
  from django import template
5
6
  from django.templatetags.static import static as django_static
@@ -113,4 +114,35 @@ def format_json(value):
113
114
  elif isinstance(value, dict | list):
114
115
  value = json.dumps(value, indent=2)
115
116
 
116
- return mark_safe(value) # noqa: S308
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:
@@ -366,10 +366,17 @@ def _enhance_method_field_schema(_operation_id, schema: dict, _components: dict)
366
366
 
367
367
  def _resolve_schema_reference(schema: dict, components: dict) -> dict:
368
368
  """Resolve $ref references in schema."""
369
- if "$ref" in schema:
370
- ref = schema["$ref"]
371
- return components.get("schemas", {}).get(ref.split("/")[-1], {})
372
- return schema
369
+ if "$ref" not in schema:
370
+ return schema
371
+
372
+ ref = schema["$ref"]
373
+ target = components.get("schemas", {}).get(ref.split("/")[-1], {})
374
+ # Work on a copy to avoid mutating components
375
+ resolved = dict(target) if isinstance(target, dict) else {}
376
+ for key, value in schema.items():
377
+ if key != "$ref":
378
+ resolved[key] = value
379
+ return resolved
373
380
 
374
381
 
375
382
  def _handle_all_of_schema(schema: dict, components: dict, _for_response: bool) -> dict:
@@ -401,16 +408,22 @@ def _handle_all_of_schema(schema: dict, components: dict, _for_response: bool) -
401
408
 
402
409
  def _get_explicit_value(schema: dict):
403
410
  """Get explicit value from schema (enum, example, or default)."""
404
- # Ensure schema is a dictionary
405
411
  if not isinstance(schema, dict):
406
412
  return None
407
413
 
408
414
  if "enum" in schema:
409
415
  return schema["enum"][0]
416
+
410
417
  if "example" in schema:
411
418
  return schema["example"]
419
+
412
420
  if "default" in schema:
421
+ # For array types with items schema, don't use empty default
422
+ # Let the generator create a proper example instead
423
+ if schema.get("type") == "array" and "items" in schema:
424
+ return None
413
425
  return schema["default"]
426
+
414
427
  return None
415
428
 
416
429
 
@@ -499,11 +512,7 @@ def format_schema_as_json_example(
499
512
  if description:
500
513
  result += f"{description}\n\n"
501
514
 
502
- result += "```json\n"
503
- result += json.dumps(example_json, indent=2)
504
- result += "\n```\n"
505
-
506
- return result
515
+ return json.dumps(example_json, indent=2)
507
516
 
508
517
 
509
518
  def _format_schema_for_display(
@@ -518,22 +527,122 @@ def _format_schema_for_display(
518
527
  operation_id, schema["$ref"], components, for_response
519
528
  )
520
529
 
521
- example = schema_to_example_json(operation_id, schema, components, for_response)
522
- return f"```json\n{json.dumps(example, indent=2)}\n```"
530
+ return schema_to_example_json(operation_id, schema, components, for_response)
531
+
532
+
533
+ def _generate_field_value(
534
+ field_name: str,
535
+ prop_schema: dict,
536
+ operation_id: str,
537
+ components: dict,
538
+ is_response: bool = True,
539
+ ) -> Any:
540
+ """Generate a realistic value for a specific field based on its name and schema."""
541
+ # Get field-specific generator from settings
542
+ field_generator = get_field_generator(field_name)
543
+
544
+ if field_generator:
545
+ return field_generator(prop_schema)
546
+
547
+ # Fallback to schema-based generation
548
+ return schema_to_example_json(operation_id, prop_schema, components, is_response)
549
+
550
+
551
+ def get_field_generator(field_name: str):
552
+ """Get appropriate generator function for a field name from settings."""
553
+ return drf_to_mkdoc_settings.FIELD_GENERATORS.get(field_name.lower())
554
+
555
+
556
+ def _generate_examples(operation_id: str, schema: dict, components: dict) -> list:
557
+ """Generate examples for a schema."""
558
+
559
+ if "$ref" in schema:
560
+ schema = _resolve_schema_reference(schema, components)
561
+
562
+ examples = []
563
+
564
+ # Handle object with array properties
565
+ if schema.get("type") == "object" and "properties" in schema:
566
+ empty_example = {}
567
+ populated_example = {}
568
+ has_array_default = False
569
+
570
+ # Check for array fields with default=[]
571
+ for _prop_name, prop_schema in schema["properties"].items():
572
+ resolved_prop_schema = (
573
+ _resolve_schema_reference(prop_schema, components)
574
+ if "$ref" in prop_schema
575
+ else prop_schema
576
+ )
577
+ if (
578
+ resolved_prop_schema.get("type") == "array"
579
+ and resolved_prop_schema.get("default") == []
580
+ ):
581
+ has_array_default = True
582
+ break
583
+
584
+ # Generate examples
585
+ for prop_name, prop_schema in schema["properties"].items():
586
+ resolved_prop_schema = (
587
+ _resolve_schema_reference(prop_schema, components)
588
+ if "$ref" in prop_schema
589
+ else prop_schema
590
+ )
591
+
592
+ if (
593
+ resolved_prop_schema.get("type") == "array"
594
+ and resolved_prop_schema.get("default") == []
595
+ ):
596
+ empty_example[prop_name] = []
597
+ items_schema = resolved_prop_schema.get("items", {})
598
+ populated_example[prop_name] = [
599
+ _generate_field_value(
600
+ prop_name, items_schema, operation_id, components, True
601
+ )
602
+ ]
603
+ else:
604
+ value = _generate_field_value(
605
+ prop_name, resolved_prop_schema, operation_id, components, True
606
+ )
607
+ empty_example[prop_name] = value
608
+ populated_example[prop_name] = value
609
+
610
+ if has_array_default:
611
+ examples.append(empty_example)
612
+ examples.append(populated_example)
613
+ else:
614
+ examples.append(empty_example)
615
+
616
+ # Handle array field with default=[]
617
+ elif schema.get("type") == "array" and schema.get("default") == []:
618
+ examples.append([])
619
+ items_schema = schema.get("items", {})
620
+ populated_example = [
621
+ _generate_field_value("items", items_schema, operation_id, components, True)
622
+ ]
623
+ examples.append(populated_example)
624
+ else:
625
+ example = _generate_field_value("root", schema, operation_id, components, True)
626
+ examples.append(example)
627
+
628
+ return examples
523
629
 
524
630
 
525
631
  def _prepare_response_data(operation_id: str, responses: dict, components: dict) -> list:
526
632
  """Prepare response data for template rendering."""
633
+
527
634
  formatted_responses = []
528
635
  for status_code, response_data in responses.items():
529
636
  schema = response_data.get("content", {}).get("application/json", {}).get("schema", {})
530
- formatted_responses.append(
531
- {
532
- "status_code": status_code,
533
- "description": response_data.get("description", ""),
534
- "example": _format_schema_for_display(operation_id, schema, components, True),
535
- }
536
- )
637
+
638
+ examples = _generate_examples(operation_id, schema, components)
639
+
640
+ formatted_response = {
641
+ "status_code": status_code,
642
+ "description": response_data.get("description", ""),
643
+ "examples": examples,
644
+ }
645
+ formatted_responses.append(formatted_response)
537
646
  return formatted_responses
538
647
 
539
648
 
@@ -580,9 +689,17 @@ def create_endpoint_page(
580
689
  "stylesheets/endpoints/animations.css",
581
690
  "stylesheets/endpoints/accessibility.css",
582
691
  "stylesheets/endpoints/loading.css",
583
- "stylesheets/endpoints/try-out-sidebar.css",
692
+ "stylesheets/try-out/main.css",
693
+ ],
694
+ "scripts": [
695
+ "javascripts/try-out/modal.js",
696
+ "javascripts/try-out/response-modal.js",
697
+ "javascripts/try-out/tabs.js",
698
+ "javascripts/try-out/form-manager.js",
699
+ "javascripts/try-out/request-executor.js",
700
+ "javascripts/try-out/suggestions.js",
701
+ "javascripts/try-out/main.js",
584
702
  ],
585
- "scripts": ["javascripts/try-out-sidebar.js"],
586
703
  "prefix_path": f"{drf_to_mkdoc_settings.PROJECT_NAME}/",
587
704
  }
588
705
 
@@ -590,6 +707,9 @@ def create_endpoint_page(
590
707
  if _is_list_endpoint(method, path, operation_id):
591
708
  query_params = extract_query_parameters_from_view(operation_id)
592
709
  _add_custom_parameters(operation_id, query_params)
710
+ for key, value in query_params.items():
711
+ # Prevent duplicates while preserving order
712
+ query_params[key] = list(dict.fromkeys(value))
593
713
  context["query_parameters"] = query_params
594
714
 
595
715
  return render_to_string("endpoints/detail/base.html", context)
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drf-to-mkdoc
3
- Version: 0.2.2
3
+ Version: 0.2.4
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>
@@ -56,6 +56,8 @@ Generate beautiful, interactive Markdown API documentation from Django REST Fram
56
56
  - **Zero-hassle docs**: Beautiful, always-in-sync API docs straight from your codebase
57
57
  - **Model deep dive**: Auto-generated model pages with fields, relationships, and choices
58
58
  - **Lightning-fast discovery**: Interactive endpoint index with powerful filters and search
59
+ - **Try-it-out**: Interactive API testing directly in the documentation with request/response examples
60
+ - **AI-powered**: Optional AI-generated documentation with custom field generators(Wait for it...)
59
61
  - **DRF-native**: Works with DRF Spectacular; no custom schema wiring needed
60
62
  - **MkDocs Material**: Looks great out of the box with the Material theme
61
63
 
@@ -76,7 +78,7 @@ INSTALLED_APPS = [
76
78
 
77
79
  # Required for OpenAPI schema generation
78
80
  REST_FRAMEWORK = {
79
- 'DEFAULT_SCHEMA_CLASS': 'drf_to_mkdoc.utils.schema.AutoSchema', # Use our custom AutoSchema
81
+ 'DEFAULT_SCHEMA_CLASS': 'drf_to_mkdoc.utils.schema.AutoSchema',
80
82
  }
81
83
 
82
84
  SPECTACULAR_SETTINGS = {
@@ -99,6 +101,12 @@ DRF_TO_MKDOC = {
99
101
  # 'MODEL_DOCS_FILE': 'docs/model-docs.json',
100
102
  # 'DOC_CONFIG_FILE': 'docs/configs/doc_config.json',
101
103
  # 'CUSTOM_SCHEMA_FILE': 'docs/configs/custom_schema.json',
104
+ # 'FIELD_GENERATORS': {
105
+ # 'email': 'faker.email',
106
+ # 'name': 'faker.name',
107
+ # 'created_at': 'datetime.now',
108
+ # },
109
+ # 'ENABLE_AI_DOCS': False,
102
110
  }
103
111
  ```
104
112
 
@@ -111,18 +119,54 @@ DRF_TO_MKDOC = {
111
119
  python manage.py build_docs --settings=docs_settings
112
120
  ```
113
121
 
122
+ ### Configuration Options
123
+
124
+ The `DRF_TO_MKDOC` setting supports several configuration options:
125
+
126
+ - **`DJANGO_APPS`** (required): List of Django app names to process
127
+ - **`DOCS_DIR`**: Directory where docs will be generated (default: `docs`)
128
+ - **`CONFIG_DIR`**: Directory for configuration files (default: `docs/configs`)
129
+ - **`FIELD_GENERATORS`**: Custom field value generators for better examples
130
+ - **`ENABLE_AI_DOCS`**: Enable AI-powered documentation features (default: `False`)
131
+ - **`PATH_PARAM_SUBSTITUTE_FUNCTION`**: Custom function for path parameter substitution
132
+ - **`PATH_PARAM_SUBSTITUTE_MAPPING`**: Mapping for path parameter substitution
133
+
114
134
  ## Available Commands
115
135
 
116
136
  - `build_docs`: Build the complete documentation site with MkDocs
117
137
  - `build_endpoint_docs`: Build endpoint documentation from OpenAPI schema
118
138
  - `build_model_docs`: Build model documentation from model JSON data
119
139
  - `extract_model_data`: Extract model data from Django model introspection and save as JSON
140
+ - `generate_doc_json`: Generate JSON context for new API endpoints to be documented
120
141
  - `update_doc_schema`: Update the final schema by copying the documented schema
121
142
 
122
143
  ## What you get
123
144
 
124
145
  See a detailed overview of generated files in `docs/structure.md` and a feature breakdown in `docs/features.md`.
125
146
 
147
+ ## Key Features
148
+
149
+ ### 🚀 Interactive API Testing (Try-Out)
150
+ - **Live API testing**: Test endpoints directly from the documentation
151
+ - **Request builder**: Interactive forms for parameters, headers, and request body
152
+ - **Response viewer**: Real-time response display with syntax highlighting
153
+ - **Floating action button**: Easy access to testing interface
154
+ - **Multiple examples**: Support for both empty and populated response examples
155
+
156
+ ### 🤖 AI-Powered Documentation
157
+ - **Custom field generators**: Define custom value generators for specific fields
158
+ - **AI documentation**: Optional AI-generated documentation with context analysis
159
+ - **Smart examples**: Enhanced example generation for better API understanding
160
+
161
+ ### 📊 Advanced Filtering & Search
162
+ - **Multi-criteria filtering**: Filter by app, HTTP method, path, and search terms
163
+ - **Real-time search**: Instant search across all endpoints
164
+ - **Smart suggestions**: Auto-complete for query parameters and field names
165
+
166
+ ### 🎨 Beautiful UI
167
+ - **Material Design**: Modern, responsive interface with dark/light themes
168
+ - **Interactive elements**: Hover effects, animations, and smooth transitions
169
+ - **Mobile-friendly**: Fully responsive design for all devices
126
170
 
127
171
  ## How it works
128
172
 
@@ -167,13 +211,29 @@ drf-to-mkdoc/
167
211
  │ │ ├── build_endpoint_docs.py # Build endpoint documentation
168
212
  │ │ ├── build_model_docs.py # Build model documentation
169
213
  │ │ ├── extract_model_data.py # Extract model data from Django
214
+ │ │ ├── generate_doc_json.py # Generate JSON context for AI docs
170
215
  │ │ └── update_doc_schema.py # Schema updates
216
+ │ ├── static/
217
+ │ │ └── drf-to-mkdoc/
218
+ │ │ ├── javascripts/
219
+ │ │ │ ├── try-out/ # Interactive API testing
220
+ │ │ │ └── endpoints-filter.js # Endpoint filtering
221
+ │ │ └── stylesheets/ # CSS for styling
222
+ │ ├── templates/
223
+ │ │ ├── endpoints/ # Endpoint documentation templates
224
+ │ │ ├── model_detail/ # Model documentation templates
225
+ │ │ └── try-out/ # Interactive testing templates
171
226
  │ └── utils/
172
- │ ├── common.py # Shared utilities
173
- │ ├── endpoint_generator.py # Endpoint documentation
174
- │ ├── model_generator.py # Model documentation
175
- └── extractors/ # Query parameter extraction
176
- ├── pyproject.toml # Project configuration
227
+ │ ├── ai_tools/ # AI-powered documentation features
228
+ │ ├── commons/ # Shared utilities
229
+ │ ├── extractors/ # Query parameter extraction
230
+ ├── endpoint_detail_generator.py
231
+ ├── endpoint_list_generator.py
232
+ │ ├── model_detail_generator.py
233
+ │ ├── model_list_generator.py
234
+ │ └── schema.py
235
+ ├── docs/ # Generated documentation
236
+ ├── pyproject.toml # Project configuration
177
237
  └── README.md
178
238
  ```
179
239
 
@@ -232,5 +292,4 @@ your-project/
232
292
 
233
293
  ## Contributing
234
294
 
235
- See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
236
- This will ensure that only the source configuration and scripts are versioned, while the generated documentation is excluded.
295
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.