drf-to-mkdoc 0.2.1__py3-none-any.whl → 0.2.2__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 (33) hide show
  1. drf_to_mkdoc/conf/settings.py +0 -2
  2. drf_to_mkdoc/templates/endpoints/detail/base.html +33 -0
  3. drf_to_mkdoc/templates/endpoints/detail/path_parameters.html +8 -0
  4. drf_to_mkdoc/templates/endpoints/detail/query_parameters.html +43 -0
  5. drf_to_mkdoc/templates/endpoints/detail/request_body.html +10 -0
  6. drf_to_mkdoc/templates/endpoints/detail/responses.html +18 -0
  7. drf_to_mkdoc/templates/endpoints/list/base.html +23 -0
  8. drf_to_mkdoc/templates/endpoints/list/endpoint_card.html +18 -0
  9. drf_to_mkdoc/templates/endpoints/list/filter_section.html +16 -0
  10. drf_to_mkdoc/templates/endpoints/list/filters/app.html +8 -0
  11. drf_to_mkdoc/templates/endpoints/list/filters/method.html +12 -0
  12. drf_to_mkdoc/templates/endpoints/list/filters/path.html +5 -0
  13. drf_to_mkdoc/templates/endpoints/list/filters/search.html +9 -0
  14. drf_to_mkdoc/templates/model_detail/base.html +34 -0
  15. drf_to_mkdoc/templates/model_detail/choices.html +12 -0
  16. drf_to_mkdoc/templates/model_detail/fields.html +11 -0
  17. drf_to_mkdoc/templates/model_detail/meta.html +6 -0
  18. drf_to_mkdoc/templates/model_detail/methods.html +9 -0
  19. drf_to_mkdoc/templates/model_detail/relationships.html +8 -0
  20. drf_to_mkdoc/templates/models_index.html +24 -0
  21. drf_to_mkdoc/templatetags/custom_filters.py +116 -0
  22. drf_to_mkdoc/utils/endpoint_detail_generator.py +79 -168
  23. drf_to_mkdoc/utils/endpoint_list_generator.py +58 -193
  24. drf_to_mkdoc/utils/model_detail_generator.py +22 -202
  25. drf_to_mkdoc/utils/model_list_generator.py +26 -44
  26. drf_to_mkdoc/utils/schema.py +1 -1
  27. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.2.dist-info}/METADATA +1 -1
  28. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.2.dist-info}/RECORD +31 -13
  29. drf_to_mkdoc/utils/md_generators/__init__.py +0 -0
  30. drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +0 -72
  31. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.2.dist-info}/WHEEL +0 -0
  32. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.2.dist-info}/licenses/LICENSE +0 -0
  33. {drf_to_mkdoc-0.2.1.dist-info → drf_to_mkdoc-0.2.2.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from collections import defaultdict
6
6
  from typing import Any
7
7
 
8
8
  from django.apps import apps
9
- from django.templatetags.static import static
9
+ from django.template.loader import render_to_string
10
10
  from rest_framework import serializers
11
11
 
12
12
  from drf_to_mkdoc.conf.settings import drf_to_mkdoc_settings
@@ -14,16 +14,12 @@ from drf_to_mkdoc.utils.commons.file_utils import write_file
14
14
  from drf_to_mkdoc.utils.commons.operation_utils import (
15
15
  extract_app_from_operation_id,
16
16
  extract_viewset_name_from_operation_id,
17
- format_method_badge,
18
17
  )
19
18
  from drf_to_mkdoc.utils.commons.path_utils import create_safe_filename
20
19
  from drf_to_mkdoc.utils.commons.schema_utils import get_custom_schema
21
20
  from drf_to_mkdoc.utils.extractors.query_parameter_extractors import (
22
21
  extract_query_parameters_from_view,
23
22
  )
24
- from drf_to_mkdoc.utils.md_generators.query_parameters_generators import (
25
- generate_query_parameters_md,
26
- )
27
23
 
28
24
  logger = logging.getLogger()
29
25
 
@@ -510,107 +506,93 @@ def format_schema_as_json_example(
510
506
  return result
511
507
 
512
508
 
513
- def create_endpoint_page(
514
- path: str, method: str, endpoint_data: dict[str, Any], components: dict[str, Any]
515
- ) -> str:
516
- """Create a documentation page for a single API endpoint."""
517
- operation_id = endpoint_data.get("operationId", "")
518
- summary = endpoint_data.get("summary", "")
519
- description = endpoint_data.get("description", "")
520
- parameters = endpoint_data.get("parameters", [])
521
- request_body = endpoint_data.get("requestBody", {})
522
- responses = endpoint_data.get("responses", {})
523
-
524
- content = _create_endpoint_header(path, method, operation_id, summary, description)
525
- content += _add_path_parameters(parameters)
526
- content += _add_query_parameters(method, path, operation_id)
527
- content += _add_request_body(operation_id, request_body, components)
528
- content += _add_responses(operation_id, responses, components)
529
-
530
- return content
531
-
532
-
533
- def _create_endpoint_header(
534
- path: str, method: str, operation_id: str, summary: str, description: str
509
+ def _format_schema_for_display(
510
+ operation_id: str, schema: dict, components: dict, for_response: bool = True
535
511
  ) -> str:
536
- """Create the header section of the endpoint documentation."""
537
- stylesheets = [
538
- "stylesheets/endpoints/endpoint-content.css",
539
- "stylesheets/endpoints/badges.css",
540
- "stylesheets/endpoints/base.css",
541
- "stylesheets/endpoints/responsive.css",
542
- "stylesheets/endpoints/theme-toggle.css",
543
- "stylesheets/endpoints/layout.css",
544
- "stylesheets/endpoints/sections.css",
545
- "stylesheets/endpoints/animations.css",
546
- "stylesheets/endpoints/accessibility.css",
547
- "stylesheets/endpoints/loading.css",
548
- "stylesheets/endpoints/try-out-sidebar.css",
549
- ]
550
- scripts = [
551
- "javascripts/try-out-sidebar.js",
552
- ]
553
- prefix_path = f"{drf_to_mkdoc_settings.PROJECT_NAME}/"
554
- css_links = "\n".join(
555
- f'<link rel="stylesheet" href="{static(prefix_path + path)}">' for path in stylesheets
556
- )
557
- js_scripts = "\n".join(
558
- f'<script src="{static(prefix_path + path)}" defer></script>' for path in scripts
559
- )
560
- content = f"""
561
- <!-- inject CSS and JS directly -->
562
- {css_links}
563
- {js_scripts}
564
- """
565
- content += f"# {method.upper()} {path}\n\n"
566
- content += f"{format_method_badge(method)} `{path}`\n\n"
567
- content += f"**View class:** {extract_viewset_name_from_operation_id(operation_id)}\n\n"
568
-
569
- if summary:
570
- content += f"## Overview\n\n{summary}\n\n"
571
- if operation_id:
572
- content += f"**Operation ID:** `{operation_id}`\n\n"
573
- if description:
574
- content += f"{description}\n\n"
575
-
576
- return content
577
-
578
-
579
- def _add_path_parameters(parameters: list[dict]) -> str:
580
- """Add path parameters section to the documentation."""
581
- path_params = [p for p in parameters if p.get("in") == "path"]
582
- if not path_params:
512
+ """Format schema as a displayable string with JSON example."""
513
+ if not schema:
583
514
  return ""
584
515
 
585
- content = "## Path Parameters\n\n"
586
- content += "| Name | Type | Required | Description |\n"
587
- content += "|------|------|----------|-------------|\n"
588
-
589
- for param in path_params:
590
- name = param.get("name", "")
591
- param_type = param.get("schema", {}).get("type", "string")
592
- required = "Yes" if param.get("required", False) else "No"
593
- desc = param.get("description", "")
594
- content += f"| `{name}` | `{param_type}` | {required} | {desc} |\n"
516
+ if "$ref" in schema:
517
+ return format_schema_as_json_example(
518
+ operation_id, schema["$ref"], components, for_response
519
+ )
595
520
 
596
- content += "\n"
597
- return content
521
+ example = schema_to_example_json(operation_id, schema, components, for_response)
522
+ return f"```json\n{json.dumps(example, indent=2)}\n```"
598
523
 
599
524
 
600
- def _add_query_parameters(method: str, path: str, operation_id: str) -> str:
601
- """Add query parameters section for list endpoints."""
602
- is_list_endpoint = _is_list_endpoint(method, path, operation_id)
603
- if not is_list_endpoint:
604
- return ""
525
+ def _prepare_response_data(operation_id: str, responses: dict, components: dict) -> list:
526
+ """Prepare response data for template rendering."""
527
+ formatted_responses = []
528
+ for status_code, response_data in responses.items():
529
+ 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
+ )
537
+ return formatted_responses
605
538
 
606
- query_params = extract_query_parameters_from_view(operation_id)
607
- _add_custom_parameters(operation_id, query_params)
608
539
 
609
- query_params_content = generate_query_parameters_md(query_params)
610
- if query_params_content and not query_params_content.startswith("**Error:**"):
611
- return "## Query Parameters\n\n" + query_params_content
540
+ def create_endpoint_page(
541
+ path: str, method: str, endpoint_data: dict[str, Any], components: dict[str, Any]
542
+ ) -> str:
543
+ """Create a documentation page for a single API endpoint."""
544
+ operation_id = endpoint_data.get("operationId", "")
545
+ request_schema = (
546
+ endpoint_data.get("requestBody", {})
547
+ .get("content", {})
548
+ .get("application/json", {})
549
+ .get("schema")
550
+ )
612
551
 
613
- return ""
552
+ # Prepare template context
553
+ context = {
554
+ "path": path,
555
+ "method": method,
556
+ "operation_id": operation_id,
557
+ "summary": endpoint_data.get("summary", ""),
558
+ "description": endpoint_data.get("description", ""),
559
+ "viewset_name": extract_viewset_name_from_operation_id(operation_id),
560
+ "path_params": [
561
+ p for p in endpoint_data.get("parameters", []) if p.get("in") == "path"
562
+ ],
563
+ "request_body": endpoint_data.get("requestBody", {}),
564
+ "request_example": _format_schema_for_display(
565
+ operation_id, request_schema, components, False
566
+ )
567
+ if request_schema
568
+ else "",
569
+ "responses": _prepare_response_data(
570
+ operation_id, endpoint_data.get("responses", {}), components
571
+ ),
572
+ "stylesheets": [
573
+ "stylesheets/endpoints/endpoint-content.css",
574
+ "stylesheets/endpoints/badges.css",
575
+ "stylesheets/endpoints/base.css",
576
+ "stylesheets/endpoints/responsive.css",
577
+ "stylesheets/endpoints/theme-toggle.css",
578
+ "stylesheets/endpoints/layout.css",
579
+ "stylesheets/endpoints/sections.css",
580
+ "stylesheets/endpoints/animations.css",
581
+ "stylesheets/endpoints/accessibility.css",
582
+ "stylesheets/endpoints/loading.css",
583
+ "stylesheets/endpoints/try-out-sidebar.css",
584
+ ],
585
+ "scripts": ["javascripts/try-out-sidebar.js"],
586
+ "prefix_path": f"{drf_to_mkdoc_settings.PROJECT_NAME}/",
587
+ }
588
+
589
+ # Add query parameters if it's a list endpoint
590
+ if _is_list_endpoint(method, path, operation_id):
591
+ query_params = extract_query_parameters_from_view(operation_id)
592
+ _add_custom_parameters(operation_id, query_params)
593
+ context["query_parameters"] = query_params
594
+
595
+ return render_to_string("endpoints/detail/base.html", context)
614
596
 
615
597
 
616
598
  def _is_list_endpoint(method: str, path: str, operation_id: str) -> bool:
@@ -632,77 +614,6 @@ def _add_custom_parameters(operation_id: str, query_params: dict) -> None:
632
614
  query_params[queryparam_type].append(parameter["name"])
633
615
 
634
616
 
635
- def _add_request_body(operation_id: str, request_body: dict, components: dict[str, Any]) -> str:
636
- """Add request body section to the documentation."""
637
- if not request_body:
638
- return ""
639
-
640
- content = "## Request Body\n\n"
641
- req_schema = request_body.get("content", {}).get("application/json", {}).get("schema")
642
-
643
- if req_schema and "$ref" in req_schema:
644
- content += (
645
- format_schema_as_json_example(
646
- operation_id, req_schema["$ref"], components, for_response=False
647
- )
648
- + "\n"
649
- )
650
-
651
- return content
652
-
653
-
654
- def _add_responses(operation_id: str, responses: dict, components: dict[str, Any]) -> str:
655
- """Add responses section to the documentation."""
656
- if not responses:
657
- return ""
658
-
659
- content = "## Responses\n\n"
660
- for status_code, response_data in responses.items():
661
- content += _format_single_response(operation_id, status_code, response_data, components)
662
-
663
- return content
664
-
665
-
666
- def _format_single_response(
667
- operation_id: str, status_code: str, response_data: dict, components: dict[str, Any]
668
- ) -> str:
669
- """Format a single response entry."""
670
- content = f"### {status_code}\n\n"
671
-
672
- if desc := response_data.get("description", ""):
673
- content += f"{desc}\n\n"
674
-
675
- resp_schema = response_data.get("content", {}).get("application/json", {}).get("schema", {})
676
-
677
- content += _format_response_schema(operation_id, resp_schema, components)
678
- return content
679
-
680
-
681
- def _format_response_schema(
682
- operation_id: str, resp_schema: dict, components: dict[str, Any]
683
- ) -> str:
684
- """Format the response schema as JSON example."""
685
- if "$ref" in resp_schema:
686
- return (
687
- format_schema_as_json_example(
688
- operation_id, resp_schema["$ref"], components, for_response=True
689
- )
690
- + "\n"
691
- )
692
- if resp_schema.get("type") == "array" and "$ref" in resp_schema.get("items", {}):
693
- item_ref = resp_schema["items"]["$ref"]
694
- return (
695
- format_schema_as_json_example(operation_id, item_ref, components, for_response=True)
696
- + "\n"
697
- )
698
- content = "```json\n"
699
- content += json.dumps(
700
- schema_to_example_json(operation_id, resp_schema, components), indent=2
701
- )
702
- content += "\n```\n"
703
- return content
704
-
705
-
706
617
  def parse_endpoints_from_schema(paths: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
707
618
  """Parse endpoints from OpenAPI schema and organize by app"""
708
619
 
@@ -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 = set(
13
- active_filters
14
- or [
15
- "method",
16
- "path",
17
- "app",
18
- "models",
19
- "auth",
20
- "roles",
21
- "content_type",
22
- "params",
23
- "schema",
24
- "pagination",
25
- "ordering",
26
- "search",
27
- "tags",
28
- ]
29
- )
30
-
31
- def create_endpoint_card(
32
- self, endpoint: dict[str, Any], app_name: str, viewset_name: str
33
- ) -> str:
34
- method = endpoint["method"]
35
- path = endpoint["path"]
36
- filename = endpoint["filename"]
37
- view_class = extract_viewset_from_operation_id(endpoint["operation_id"])
38
-
39
- link_url = f"{app_name}/{viewset_name.lower()}/{filename}".replace(".md", "/index.html")
40
- data_attrs = f"""
41
- data-method="{method.lower()}"
42
- data-path="{path.lower()}"
43
- data-app="{app_name.lower()}"
44
- data-auth="{str(endpoint.get("auth_required", False)).lower()}"
45
- data-pagination="{str(endpoint.get("pagination_support", False)).lower()}"
46
- data-search="{str(bool(getattr(view_class, "search_fields", []))).lower()}"
47
- data-ordering="{str(endpoint.get("ordering_support", False)).lower()}"
48
- data-models="{" ".join(endpoint.get("related_models", [])).lower()}"
49
- data-roles="{" ".join(endpoint.get("permission_roles", [])).lower()}"
50
- data-content-type="{endpoint.get("content_type", "").lower()}"
51
- data-tags="{" ".join(endpoint.get("tags", [])).lower()}"
52
- data-schema="{" ".join(endpoint.get("schema_fields", [])).lower()}"
53
- data-params="{" ".join(endpoint.get("query_parameters", [])).lower()}"
54
- """.strip()
55
-
56
- return f"""
57
- <a href="{link_url}" class="endpoint-card" {data_attrs}>
58
- <span class="method-badge method-{method.lower()}">{method}</span>
59
- <span class="endpoint-path">{path}</span>
60
- </a>
61
- """
62
-
63
- def create_filter_section(self) -> str:
64
- filter_fields = {
65
- "method": """<div class="filter-group">
66
- <label class="filter-label">HTTP Method</label>
67
- <select id="filter-method" class="filter-select">
68
- <option value="">All</option>
69
- <option value="get">GET</option>
70
- <option value="post">POST</option>
71
- <option value="put">PUT</option>
72
- <option value="patch">PATCH</option>
73
- <option value="delete">DELETE</option>
74
- </select>
75
- </div>""",
76
- "path": """<div class="filter-group">
77
- <label class="filter-label">Endpoint Path</label>
78
- <input type="text" id="filter-path" class="filter-input"
79
- placeholder="Search path...">
80
- </div>""",
81
- "app": """<div class="filter-group">
82
- <label class="filter-label">Django App</label>
83
- <select id="filter-app" class="filter-select">
84
- <option value="">All</option>
85
- <!-- Dynamically filled -->
86
- </select>
87
- </div>""",
88
- "models": """<div class="filter-group">
89
- <label class="filter-label">Related Models</label>
90
- <input type="text" id="filter-models" class="filter-input">
91
- </div>""",
92
- "auth": """<div class="filter-group">
93
- <label class="filter-label">Authentication Required</label>
94
- <select id="filter-auth" class="filter-select">
95
- <option value="">All</option>
96
- <option value="true">Yes</option>
97
- <option value="false">No</option>
98
- </select>
99
- </div>""",
100
- "roles": """<div class="filter-group">
101
- <label class="filter-label">Permission Roles</label>
102
- <input type="text" id="filter-roles" class="filter-input">
103
- </div>""",
104
- "content_type": """<div class="filter-group">
105
- <label class="filter-label">Content Type</label>
106
- <input type="text" id="filter-content-type" class="filter-input">
107
- </div>""",
108
- "params": """<div class="filter-group">
109
- <label class="filter-label">Query Parameters</label>
110
- <input type="text" id="filter-params" class="filter-input">
111
- </div>""",
112
- "schema": """<div class="filter-group">
113
- <label class="filter-label">Schema Fields</label>
114
- <input type="text" id="filter-schema" class="filter-input">
115
- </div>""",
116
- "pagination": """<div class="filter-group">
117
- <label class="filter-label">Pagination Support</label>
118
- <select id="filter-pagination" class="filter-select">
119
- <option value="">All</option>
120
- <option value="true">Yes</option>
121
- <option value="false">No</option>
122
- </select>
123
- </div>""",
124
- "ordering": """<div class="filter-group">
125
- <label class="filter-label">Ordering Support</label>
126
- <select id="filter-ordering" class="filter-select">
127
- <option value="">All</option>
128
- <option value="true">Yes</option>
129
- <option value="false">No</option>
130
- </select>
131
- </div>""",
132
- "search": """<div class="filter-group">
133
- <label class="filter-label">Search Support</label>
134
- <select id="filter-search" class="filter-select">
135
- <option value="">All</option>
136
- <option value="true">Yes</option>
137
- <option value="false">No</option>
138
- </select>
139
- </div>""",
140
- "tags": """<div class="filter-group">
141
- <label class="filter-label">Tags</label>
142
- <input type="text" id="filter-tags" class="filter-input">
143
- </div>""",
144
- }
145
-
146
- fields_html = "\n".join(
147
- [html for key, html in filter_fields.items() if (key in self.active_filters)]
148
- )
149
-
150
- return f"""
151
- <div class="filter-sidebar collapsed" id="filterSidebar">
152
- <h3 class="filter-title">🔍 Filters</h3>
153
- <div class="filter-grid">
154
- {fields_html}
155
- </div>
156
-
157
- <div class="filter-actions">
158
- <button class="filter-apply" onclick="applyFilters()">Apply</button>
159
- <button class="filter-clear" onclick="clearFilters()">Clear</button>
160
- </div>
161
-
162
- <div class="filter-results">Showing 0 endpoints</div>
163
- </div>
164
- """
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
- "stylesheets/endpoints/variables.css",
171
- "stylesheets/endpoints/base.css",
172
- "stylesheets/endpoints/theme-toggle.css",
173
- "stylesheets/endpoints/filter-section.css",
174
- "stylesheets/endpoints/layout.css",
175
- "stylesheets/endpoints/endpoints-grid.css",
176
- "stylesheets/endpoints/badges.css",
177
- "stylesheets/endpoints/endpoint-content.css",
178
- "stylesheets/endpoints/tags.css",
179
- "stylesheets/endpoints/sections.css",
180
- "stylesheets/endpoints/stats.css",
181
- "stylesheets/endpoints/loading.css",
182
- "stylesheets/endpoints/animations.css",
183
- "stylesheets/endpoints/responsive.css",
184
- "stylesheets/endpoints/accessibility.css",
185
- "stylesheets/endpoints/fixes.css",
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
- content = f"""# API Endpoints
201
- <!-- inject CSS and JS directly -->
202
- {css_links}
203
- {js_scripts}
204
-
205
- <div class="main-content">
206
- """
207
- content += self.create_filter_section()
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
- for app_name, endpoints in endpoints_by_app.items():
210
- content += f'<h2>{app_name.title()}</h2>\n<div class="endpoints-grid">\n'
211
- for endpoint in endpoints:
212
- viewset = endpoint["viewset"]
213
- content += self.create_endpoint_card(endpoint, app_name, viewset)
214
- content += "</div>\n"
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: