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
@@ -138,8 +138,6 @@ class DRFToMkDocSettings:
138
138
  self._validate_range(key, value)
139
139
  self._validate_dir(key, value)
140
140
 
141
- if value is None:
142
- raise AttributeError(f"Invalid DRF_TO_MKDOC setting: '{key}'")
143
141
  return value
144
142
 
145
143
  def __getattr__(self, key):
@@ -0,0 +1,33 @@
1
+ <!-- inject CSS and JS directly -->
2
+ {% load custom_filters %}
3
+ {% for stylesheet in stylesheets %}
4
+ <link rel="stylesheet" href="{% static_with_prefix stylesheet prefix_path %}">
5
+ {% endfor %}
6
+ {% for script in scripts %}
7
+ <script src="{% static_with_prefix script prefix_path %}" defer></script>
8
+ {% endfor %}
9
+
10
+ # {{ method|upper }} {{ path }}
11
+
12
+ {{ method|format_method_badge|safe }} `{{ path }}`
13
+
14
+ **View class:** {{ viewset_name }}
15
+
16
+ {% if summary %}
17
+ ## Overview
18
+
19
+ {{ summary }}
20
+ {% endif %}
21
+
22
+ {% if operation_id %}
23
+ **Operation ID:** `{{ operation_id }}`
24
+ {% endif %}
25
+
26
+ {% if description %}
27
+ {{ description|safe }}
28
+ {% endif %}
29
+
30
+ {% include "endpoints/detail/path_parameters.html" %}
31
+ {% include "endpoints/detail/query_parameters.html" %}
32
+ {% include "endpoints/detail/request_body.html" %}
33
+ {% include "endpoints/detail/responses.html" %}
@@ -0,0 +1,8 @@
1
+ {% if path_params %}
2
+ ## Path Parameters
3
+
4
+ | Name | Type | Required | Description |
5
+ |------|------|----------|-------------|
6
+ {% for param in path_params %}| `{{ param.name }}` | `{{ param.schema.type|default:"string" }}` | {{ param.required|yesno:"Yes,No" }} | {{ param.description }} |{% endfor %}
7
+ {% endif %}
8
+
@@ -0,0 +1,43 @@
1
+ {% comment %}
2
+ Check if any query parameter fields have content
3
+ {% endcomment %}
4
+
5
+ {% if query_parameters.filter_fields or query_parameters.search_fields or query_parameters.ordering_fields or query_parameters.filter_backends or query_parameters.pagination_fields %}
6
+ ## Query Parameters
7
+
8
+ {% if query_parameters.filter_fields %}
9
+ ### Filter Fields
10
+ {% for field in query_parameters.filter_fields %}
11
+ - `{{ field }}`
12
+ {% endfor %}
13
+ {% endif %}
14
+
15
+ {% if query_parameters.search_fields %}
16
+ ### Search Fields
17
+ {% for field in query_parameters.search_fields %}
18
+ - `{{ field }}`
19
+ {% endfor %}
20
+ {% endif %}
21
+
22
+ {% if query_parameters.ordering_fields %}
23
+ ### Ordering Fields
24
+ {% for field in query_parameters.ordering_fields %}
25
+ - `{{ field }}`
26
+ {% endfor %}
27
+ {% endif %}
28
+
29
+ {% if query_parameters.filter_backends %}
30
+ ### Filter Backends
31
+ {% for backend in query_parameters.filter_backends %}
32
+ - `{{ backend }}`
33
+ {% endfor %}
34
+ {% endif %}
35
+
36
+ {% if query_parameters.pagination_fields %}
37
+ ### Pagination Fields
38
+ {% for field in query_parameters.pagination_fields %}
39
+ - `{{ field }}`
40
+ {% endfor %}
41
+ {% endif %}
42
+
43
+ {% endif %}
@@ -0,0 +1,10 @@
1
+ {% load custom_filters %}
2
+
3
+ {% if request_body %}
4
+ ## Request Body
5
+
6
+ {% if request_example %}
7
+ {{ request_example|format_json }}
8
+ {% endif %}
9
+
10
+ {% endif %}
@@ -0,0 +1,18 @@
1
+ {% load custom_filters %}
2
+
3
+ {% if responses %}
4
+ ## Responses
5
+
6
+ {% for response in responses %}
7
+ ### {{ response.status_code }}
8
+
9
+ {% if response.description %}
10
+ {{ response.description }}
11
+ {% endif %}
12
+
13
+ {% if response.example %}
14
+ {{ response.example|format_json }}
15
+ {% endif %}
16
+
17
+ {% endfor %}
18
+ {% endif %}
@@ -0,0 +1,23 @@
1
+ # API Endpoints
2
+
3
+ <style>
4
+ {% for stylesheet in stylesheets %}
5
+ @import url("{{ stylesheet|safe }}");
6
+ {% endfor %}
7
+ </style>
8
+
9
+ {% for script in scripts %}
10
+ <script src="{{ script|safe }}" defer></script>
11
+ {% endfor %}
12
+
13
+ <div class="main-content">
14
+ {% include "endpoints/list/filter_section.html" %}
15
+ {% for app_name, endpoints in endpoints_by_app.items %}
16
+ <h2>{{ app_name|title }}</h2>
17
+ <div class="endpoints-grid">
18
+ {% for endpoint in endpoints %}
19
+ {% include "endpoints/list/endpoint_card.html" %}
20
+ {% endfor %}
21
+ </div>
22
+ {% endfor %}
23
+ </div>
@@ -0,0 +1,18 @@
1
+ {% load custom_filters %}
2
+ <a href="{{ endpoint.link_url }}" class="endpoint-card"
3
+ data-method="{{ endpoint.method|lower }}"
4
+ data-path="{{ endpoint.path|lower }}"
5
+ data-app="{{ app_name|lower }}"
6
+ data-auth="{{ endpoint.auth_required|default:'false'|lower }}"
7
+ data-pagination="{{ endpoint.pagination_support|default:'false'|lower }}"
8
+ data-search="{{ endpoint.view_class.search_fields|default:''|length|yesno:'true,false'|lower }}"
9
+ data-ordering="{{ endpoint.ordering_support|default:'false'|lower }}"
10
+ data-models="{{ endpoint.related_models|default:''|join:' '|lower }}"
11
+ data-roles="{{ endpoint.permission_roles|default:''|join:' '|lower }}"
12
+ data-content-type="{{ endpoint.content_type|default:''|lower }}"
13
+ data-tags="{{ endpoint.tags|default:''|join:' '|lower }}"
14
+ data-schema="{{ endpoint.schema_fields|default:''|join:' '|lower }}"
15
+ data-params="{{ endpoint.query_parameters|default:''|join:' '|lower }}">
16
+ <span class="method-badge method-{{ endpoint.method|lower }}">{{ endpoint.method }}</span>
17
+ <span class="endpoint-path">{{ endpoint.path }}</span>
18
+ </a>
@@ -0,0 +1,16 @@
1
+ <div class="filter-sidebar collapsed" id="filterSidebar">
2
+ <h3 class="filter-title">🔍 Filters</h3>
3
+ <div class="filter-grid">
4
+ {% for filter_key in active_filters %}
5
+ {% include "endpoints/list/filters/"|add:filter_key|add:".html" %}
6
+ {% endfor %}
7
+ </div>
8
+
9
+ <div class="filter-actions">
10
+ <button class="filter-apply" onclick="applyFilters()">Apply</button>
11
+ <button class="filter-clear" onclick="clearFilters()">Clear</button>
12
+ </div>
13
+
14
+ <div class="filter-results">Showing 0 endpoints</div>
15
+ </div>
16
+
@@ -0,0 +1,8 @@
1
+ <div class="filter-group">
2
+ <label class="filter-label">Django App</label>
3
+ <select id="filter-app" class="filter-select">
4
+ <option value="">All</option>
5
+ <!-- Dynamically filled -->
6
+ </select>
7
+ </div>
8
+
@@ -0,0 +1,12 @@
1
+ <div class="filter-group">
2
+ <label class="filter-label">HTTP Method</label>
3
+ <select id="filter-method" class="filter-select">
4
+ <option value="">All</option>
5
+ <option value="get">GET</option>
6
+ <option value="post">POST</option>
7
+ <option value="put">PUT</option>
8
+ <option value="patch">PATCH</option>
9
+ <option value="delete">DELETE</option>
10
+ </select>
11
+ </div>
12
+
@@ -0,0 +1,5 @@
1
+ <div class="filter-group">
2
+ <label class="filter-label">Endpoint Path</label>
3
+ <input type="text" id="filter-path" class="filter-input" placeholder="Search path...">
4
+ </div>
5
+
@@ -0,0 +1,9 @@
1
+ <div class="filter-group">
2
+ <label class="filter-label">Search Support</label>
3
+ <select id="filter-search" class="filter-select">
4
+ <option value="">All</option>
5
+ <option value="true">Yes</option>
6
+ <option value="false">No</option>
7
+ </select>
8
+ </div>
9
+
@@ -0,0 +1,34 @@
1
+ # {{ name }}
2
+
3
+ <!-- inject CSS directly -->
4
+ {% for stylesheet in stylesheets %}
5
+ <link rel="stylesheet" href="{{ stylesheet }}">
6
+ {% endfor %}
7
+
8
+ **App:** {{ app_label }}
9
+
10
+ **Table:** `{{ table_name }}`
11
+
12
+ ## Description
13
+
14
+ {{ description }}
15
+
16
+ {% if fields %}
17
+ {% include "model_detail/fields.html" with fields=fields %}
18
+ {% endif %}
19
+
20
+ {% if has_choices %}
21
+ {% include "model_detail/choices.html" with fields=fields %}
22
+ {% endif %}
23
+
24
+ {% if relationships %}
25
+ {% include "model_detail/relationships.html" with relationships=relationships %}
26
+ {% endif %}
27
+
28
+ {% if methods %}
29
+ {% include "model_detail/methods.html" with methods=methods %}
30
+ {% endif %}
31
+
32
+ {% if meta_options %}
33
+ {% include "model_detail/meta.html" with meta_options=meta_options %}
34
+ {% endif %}
@@ -0,0 +1,12 @@
1
+ {% load custom_filters %}
2
+ ## Choices
3
+
4
+ {% for field_name, field_info in fields.items %}{% if field_info.choices %}
5
+ ### {{ field_name }} Choices
6
+
7
+ | Label | Value |
8
+ |-------|-------|
9
+ {% for choice in field_info.choices %}| {{ choice.display }} | `{{ choice.value }}` |
10
+ {% endfor %}
11
+
12
+ {% endif %}{% endfor %}
@@ -0,0 +1,11 @@
1
+ {% load custom_filters %}
2
+ ## Fields
3
+
4
+ | Field | Type | Description | Extra |
5
+ |-------|------|-------------|-------|
6
+ {% for field_name, field_info in fields.items %}{% with field_type=field_info.type|default:"Unknown" %}{% if field_type == "TextField" %}| `{{ field_name }}` | TextField | {{ field_name }} | {% if field_info.max_length %}max_length={{ field_info.max_length }}{% endif %} |
7
+ {% elif field_type == "BigAutoField" %}| `{{ field_name }}` | BigAutoField | ID | {% if field_info.blank %}blank=True{% endif %}{% if field_info.unique %}, unique=True{% endif %}{% if field_info.primary_key %}, primary_key=True{% endif %} |
8
+ {% elif field_type == "CharField" %}| `{{ field_name }}` | CharField | {{ field_name }} | {% if field_info.max_length %}max_length={{ field_info.max_length }}{% endif %}{% if field_info.null %}, null=True{% endif %}{% if field_info.blank %}, blank=True{% endif %} |
9
+ {% elif field_type == "ForeignKey" %}| `{{ field_name }}` | ForeignKey | {{ field_info.field_specific.to|default:"" }} | |
10
+ {% else %}| `{{ field_name }}` | {{ field_type }} | {{ field_name }} | {{ field_info|format_field_extra }} |
11
+ {% endif %}{% endwith %}{% endfor %}
@@ -0,0 +1,6 @@
1
+ ## Meta Options
2
+
3
+ {% for option, value in meta_options.items %}
4
+ - **{{ option }}:** {{ value }}
5
+ {% endfor %}
6
+
@@ -0,0 +1,9 @@
1
+ ## Methods
2
+
3
+ {% for method in methods %}
4
+ ### `{{ method.name }}()`
5
+
6
+ {{ method.docstring|default:"No documentation available." }}
7
+
8
+ {% endfor %}
9
+
@@ -0,0 +1,8 @@
1
+ {% load custom_filters %}
2
+ ## Relationships
3
+
4
+ | Field | Type | Related Model |
5
+ |-------|------|---------------|
6
+ {% for field_name, field_info in relationships.items %}{% with field_type=field_info.type|default:"Unknown" %}{% if field_info.field_specific.to %}| `{{ field_name }}` | {{ field_type }} | {% if "." in field_info.field_specific.to %}[{{ field_info.field_specific.to|cut:"." }}](../{{ field_info.field_specific.to|cut:"." }}/{% else %}[{{ field_info.field_specific.to }}]({{ field_info.field_specific.to|lower }}/{% endif %} |
7
+ {% endif %}{% endwith %}{% endfor %}{% for rel_name, rel_info in relationships.items %}| `{{ rel_name }}` | {{ rel_info.type|default:"Unknown" }} | [{{ rel_info.verbose_name|capfirst }}](../../{{ rel_info.app_label }}/{{ rel_info.table_name }}/) |
8
+ {% endfor %}
@@ -0,0 +1,24 @@
1
+ {% load custom_filters %}
2
+ # Django Models
3
+
4
+ This section contains documentation for all Django models in the system, organized by Django application.
5
+
6
+ <!-- inject CSS directly -->
7
+ {% for css in stylesheets %}
8
+ <link rel="stylesheet" href="{{ css }}">
9
+ {% endfor %}
10
+
11
+ <div class="models-container">
12
+ {% for app_name, models in sorted_models %}
13
+ <div class="app-header">{{ app_name|title|cut:"_"|safe }}</div>
14
+ <div class="app-description">{{ app_descriptions|get_item:app_name }}</div>
15
+
16
+ <div class="model-cards">
17
+ {% for verbose_name, table_name in models %}
18
+ <a href="{{ app_name|urlencode }}/{{ table_name|urlencode }}/" class="model-card">{{ verbose_name }}</a>
19
+ {% endfor %}
20
+ </div>
21
+ {% endfor %}
22
+ </div>
23
+
24
+ Each model page contains detailed field documentation, method signatures, and relationships to other models.
@@ -0,0 +1,116 @@
1
+ import html
2
+ import json
3
+
4
+ from django import template
5
+ from django.templatetags.static import static as django_static
6
+ from django.utils.safestring import mark_safe
7
+
8
+ from drf_to_mkdoc.utils.commons.operation_utils import (
9
+ format_method_badge as format_method_badge_util,
10
+ )
11
+
12
+ register = template.Library()
13
+
14
+
15
+ @register.filter
16
+ def is_foreign_key(field_type):
17
+ return field_type in ["ForeignKey", "OneToOneField"]
18
+
19
+
20
+ @register.simple_tag
21
+ def static_with_prefix(path, prefix=""):
22
+ """Add prefix to static path"""
23
+ return django_static(prefix + path)
24
+
25
+
26
+ @register.filter
27
+ def format_method_badge(method):
28
+ """Format HTTP method as badge"""
29
+ return format_method_badge_util(method)
30
+
31
+
32
+ @register.filter
33
+ def get_display_name(field_name, field_type):
34
+ if field_type in ["ForeignKey", "OneToOneField"]:
35
+ return f"{field_name}_id"
36
+ return field_name
37
+
38
+
39
+ @register.filter
40
+ def split(value, delimiter=","):
41
+ return value.split(delimiter)
42
+
43
+
44
+ @register.filter
45
+ def cut(value, arg):
46
+ return value.split(arg)[1] if "." in value else value
47
+
48
+
49
+ @register.filter
50
+ def get_item(dictionary, key):
51
+ return dictionary.get(key, "")
52
+
53
+
54
+ @register.filter
55
+ def format_field_type(field_info):
56
+ field_type = field_info.get("type", "")
57
+ if field_type == "ForeignKey":
58
+ return f"ForeignKey | {field_info.get('field_specific', {}).get('to', '')}"
59
+ if field_type == "CharField":
60
+ max_length = field_info.get("max_length", "")
61
+ return f"CharField | {field_info.get('verbose_name', '')} | max_length={max_length}"
62
+ return field_type
63
+
64
+
65
+ @register.filter
66
+ def format_field_extra(field_info):
67
+ extras = []
68
+ if field_info.get("null"):
69
+ extras.append("null=True")
70
+ if field_info.get("blank"):
71
+ extras.append("blank=True")
72
+ if field_info.get("unique"):
73
+ extras.append("unique=True")
74
+ if field_info.get("primary_key"):
75
+ extras.append("primary_key=True")
76
+ if field_info.get("max_length"):
77
+ extras.append(f"max_length={field_info['max_length']}")
78
+ return ", ".join(extras)
79
+
80
+
81
+ @register.filter
82
+ def yesno(value, arg=None):
83
+ """
84
+ Given a string mapping values for true, false and (optionally) None,
85
+ return one of those strings according to the value.
86
+ """
87
+ if arg is None:
88
+ arg = "yes,no,maybe"
89
+ bits = arg.split(",")
90
+ if len(bits) < 2:
91
+ return value # Invalid arg.
92
+ try:
93
+ yes, no, maybe = bits
94
+ except ValueError:
95
+ yes, no, maybe = bits[0], bits[1], bits[1]
96
+
97
+ if value is None:
98
+ return maybe
99
+ if value:
100
+ return yes
101
+ return no
102
+
103
+
104
+ @register.filter
105
+ def format_json(value):
106
+ if isinstance(value, str):
107
+ value = html.unescape(value)
108
+ try:
109
+ parsed = json.loads(value)
110
+ value = json.dumps(parsed, indent=2)
111
+ except (json.JSONDecodeError, TypeError):
112
+ pass
113
+ elif isinstance(value, dict | list):
114
+ value = json.dumps(value, indent=2)
115
+
116
+ return mark_safe(value) # noqa: S308