drf-to-mkdoc 0.1.0__py3-none-any.whl → 0.1.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/__init__.py +6 -6
- drf_to_mkdoc/apps.py +14 -14
- drf_to_mkdoc/conf/settings.py +44 -44
- drf_to_mkdoc/management/commands/build_docs.py +76 -76
- drf_to_mkdoc/management/commands/generate_doc_json.py +512 -512
- drf_to_mkdoc/management/commands/generate_docs.py +138 -138
- drf_to_mkdoc/management/commands/generate_model_docs.py +327 -327
- drf_to_mkdoc/management/commands/update_doc_schema.py +53 -53
- drf_to_mkdoc/utils/__init__.py +3 -3
- drf_to_mkdoc/utils/endpoint_generator.py +945 -945
- drf_to_mkdoc/utils/extractors/__init__.py +3 -3
- drf_to_mkdoc/utils/extractors/query_parameter_extractors.py +229 -229
- drf_to_mkdoc/utils/md_generators/query_parameters_generators.py +72 -72
- drf_to_mkdoc/utils/model_generator.py +269 -269
- {drf_to_mkdoc-0.1.0.dist-info → drf_to_mkdoc-0.1.3.dist-info}/METADATA +247 -247
- drf_to_mkdoc-0.1.3.dist-info/RECORD +25 -0
- {drf_to_mkdoc-0.1.0.dist-info → drf_to_mkdoc-0.1.3.dist-info}/licenses/LICENSE +21 -21
- drf_to_mkdoc-0.1.0.dist-info/RECORD +0 -25
- {drf_to_mkdoc-0.1.0.dist-info → drf_to_mkdoc-0.1.3.dist-info}/WHEEL +0 -0
- {drf_to_mkdoc-0.1.0.dist-info → drf_to_mkdoc-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Extractors for parsing Django views and extracting documentation information.
|
|
3
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Extractors for parsing Django views and extracting documentation information.
|
|
3
|
+
"""
|
|
@@ -1,229 +1,229 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""Query parameter extraction utilities for Django views."""
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def extract_query_parameters_from_view(operation_id: str) -> dict[str, Any]:
|
|
11
|
-
"""Extract query parameters from a Django view class"""
|
|
12
|
-
view_class = extract_viewset_from_operation_id(operation_id)
|
|
13
|
-
if not view_class:
|
|
14
|
-
return {
|
|
15
|
-
"search_fields": [],
|
|
16
|
-
"filter_fields": [],
|
|
17
|
-
"ordering_fields": [],
|
|
18
|
-
"filter_backends": [],
|
|
19
|
-
"pagination_fields": [],
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
"search_fields": extract_query_parameters_from_view_search_fields(view_class),
|
|
24
|
-
"filter_fields": extract_query_parameters_from_view_filter_fields(view_class),
|
|
25
|
-
"ordering_fields": extract_query_parameters_from_view_ordering_fields(view_class),
|
|
26
|
-
"filter_backends": extract_query_parameters_from_view_filter_backends(view_class),
|
|
27
|
-
"pagination_fields": extract_query_parameters_from_view_pagination_fields(view_class),
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def extract_query_parameters_from_view_search_fields(view_class: Any) -> list[str]:
|
|
32
|
-
"""Extract search fields from a Django view class"""
|
|
33
|
-
if not view_class:
|
|
34
|
-
return []
|
|
35
|
-
|
|
36
|
-
search_fields = []
|
|
37
|
-
if hasattr(view_class, "search_fields") and view_class.search_fields:
|
|
38
|
-
search_fields = sorted(view_class.search_fields)
|
|
39
|
-
|
|
40
|
-
return search_fields
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def extract_query_parameters_from_view_filter_fields(view_class: Any) -> list[str]:
|
|
44
|
-
"""Extract filter fields from a Django view class"""
|
|
45
|
-
if not view_class:
|
|
46
|
-
return []
|
|
47
|
-
|
|
48
|
-
filter_fields = []
|
|
49
|
-
if hasattr(view_class, "filterset_class"):
|
|
50
|
-
filter_fields = extract_filterset_fields(view_class.filterset_class)
|
|
51
|
-
elif hasattr(view_class, "filterset_fields") and view_class.filterset_fields:
|
|
52
|
-
filter_fields = sorted(view_class.filterset_fields)
|
|
53
|
-
|
|
54
|
-
return filter_fields
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def extract_query_parameters_from_view_ordering_fields(view_class: Any) -> list[str]:
|
|
58
|
-
"""Extract ordering fields from a Django view class"""
|
|
59
|
-
if not view_class:
|
|
60
|
-
return []
|
|
61
|
-
|
|
62
|
-
ordering_fields = []
|
|
63
|
-
if hasattr(view_class, "ordering_fields") and view_class.ordering_fields:
|
|
64
|
-
ordering_fields = sorted(view_class.ordering_fields)
|
|
65
|
-
|
|
66
|
-
return ordering_fields
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def extract_query_parameters_from_view_filter_backends(view_class: Any) -> list[str]:
|
|
70
|
-
"""Extract filter backends from a Django view class"""
|
|
71
|
-
if not view_class:
|
|
72
|
-
return []
|
|
73
|
-
|
|
74
|
-
filter_backends = []
|
|
75
|
-
if hasattr(view_class, "filter_backends") and view_class.filter_backends:
|
|
76
|
-
for backend in view_class.filter_backends:
|
|
77
|
-
filter_backends.append(getattr(backend, "__name__", str(backend)))
|
|
78
|
-
|
|
79
|
-
return filter_backends
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def extract_query_parameters_from_view_pagination_fields(view_class: Any) -> list[str]:
|
|
83
|
-
"""Extract pagination fields from a Django view class"""
|
|
84
|
-
if not view_class:
|
|
85
|
-
return []
|
|
86
|
-
|
|
87
|
-
pagination_fields = []
|
|
88
|
-
if hasattr(view_class, "pagination_class") and view_class.pagination_class:
|
|
89
|
-
pagination_class = view_class.pagination_class
|
|
90
|
-
if hasattr(pagination_class, "get_schema_fields"):
|
|
91
|
-
try:
|
|
92
|
-
# Get pagination fields from the pagination class
|
|
93
|
-
schema_fields = pagination_class().get_schema_fields(view_class())
|
|
94
|
-
pagination_fields = sorted([field.name for field in schema_fields])
|
|
95
|
-
except Exception as e:
|
|
96
|
-
# Check if it's specifically the coreapi missing error
|
|
97
|
-
if "coreapi must be installed" in str(e):
|
|
98
|
-
raise ValueError(
|
|
99
|
-
"coreapi is required for pagination schema extraction. "
|
|
100
|
-
"Install it with: pip install coreapi"
|
|
101
|
-
) from e
|
|
102
|
-
raise ValueError(
|
|
103
|
-
"Failed to get schema fields from pagination class "
|
|
104
|
-
f"{pagination_class.__name__}: {e}"
|
|
105
|
-
) from e
|
|
106
|
-
else:
|
|
107
|
-
raise ValueError(
|
|
108
|
-
f"Pagination class {pagination_class.__name__} "
|
|
109
|
-
"must implement get_schema_fields() method"
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
return pagination_fields
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _extract_filterset_fields_from_class_attributes(filterset_class: Any) -> list[str]:
|
|
116
|
-
fields = []
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
import django_filters
|
|
120
|
-
|
|
121
|
-
# Get all class attributes, including inherited ones
|
|
122
|
-
for attr_name in dir(filterset_class):
|
|
123
|
-
# Skip private attributes and known non-filter attributes
|
|
124
|
-
if attr_name.startswith("_") or attr_name in [
|
|
125
|
-
"Meta",
|
|
126
|
-
"form",
|
|
127
|
-
"queryset",
|
|
128
|
-
"request",
|
|
129
|
-
"errors",
|
|
130
|
-
"qs",
|
|
131
|
-
"is_valid",
|
|
132
|
-
]:
|
|
133
|
-
continue
|
|
134
|
-
|
|
135
|
-
try:
|
|
136
|
-
attr = getattr(filterset_class, attr_name)
|
|
137
|
-
if isinstance(attr, django_filters.Filter):
|
|
138
|
-
if attr_name not in fields:
|
|
139
|
-
fields.append(attr_name)
|
|
140
|
-
except (AttributeError, TypeError):
|
|
141
|
-
continue
|
|
142
|
-
|
|
143
|
-
except ImportError:
|
|
144
|
-
# django_filters not available, skip this strategy
|
|
145
|
-
pass
|
|
146
|
-
|
|
147
|
-
return fields
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _extract_filterset_fields_from_meta(filterset_class: Any) -> list[str]:
|
|
151
|
-
fields = []
|
|
152
|
-
|
|
153
|
-
if hasattr(filterset_class, "Meta") and hasattr(filterset_class.Meta, "fields"):
|
|
154
|
-
meta_fields = filterset_class.Meta.fields
|
|
155
|
-
if isinstance(meta_fields, list | tuple):
|
|
156
|
-
# List/tuple format: ['field1', 'field2']
|
|
157
|
-
for field in meta_fields:
|
|
158
|
-
if field not in fields:
|
|
159
|
-
fields.append(field)
|
|
160
|
-
elif isinstance(meta_fields, dict):
|
|
161
|
-
# Dictionary format: {'field1': ['exact'], 'field2': ['icontains']}
|
|
162
|
-
for field in meta_fields:
|
|
163
|
-
if field not in fields:
|
|
164
|
-
fields.append(field)
|
|
165
|
-
|
|
166
|
-
return fields
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
def _extract_filterset_fields_from_internal_attrs(filterset_class: Any) -> list[str]:
|
|
170
|
-
fields = []
|
|
171
|
-
|
|
172
|
-
# Use Django's internal FilterSet attributes as fallback
|
|
173
|
-
# This handles cases where the above strategies might miss some filters
|
|
174
|
-
for internal_attr in ["declared_filters", "base_filters"]:
|
|
175
|
-
if hasattr(filterset_class, internal_attr):
|
|
176
|
-
try:
|
|
177
|
-
internal_filters = getattr(filterset_class, internal_attr)
|
|
178
|
-
if hasattr(internal_filters, "keys"):
|
|
179
|
-
for field in internal_filters:
|
|
180
|
-
if field not in fields:
|
|
181
|
-
fields.append(field)
|
|
182
|
-
except (AttributeError, TypeError):
|
|
183
|
-
continue
|
|
184
|
-
|
|
185
|
-
return fields
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _extract_filterset_fields_from_get_fields(filterset_class: Any) -> list[str]:
|
|
189
|
-
fields = []
|
|
190
|
-
|
|
191
|
-
# Try get_fields() method if available (for dynamic filters)
|
|
192
|
-
if hasattr(filterset_class, "get_fields"):
|
|
193
|
-
filterset_instance = filterset_class()
|
|
194
|
-
filterset_fields = filterset_instance.get_fields()
|
|
195
|
-
if filterset_fields and hasattr(filterset_fields, "keys"):
|
|
196
|
-
for field in filterset_fields:
|
|
197
|
-
if field not in fields:
|
|
198
|
-
fields.append(field)
|
|
199
|
-
|
|
200
|
-
return fields
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def extract_filterset_fields(filterset_class: Any) -> list[str]:
|
|
204
|
-
"""Extract field names from a Django FilterSet class
|
|
205
|
-
|
|
206
|
-
This function uses multiple strategies to comprehensively detect all filter fields:
|
|
207
|
-
1. Check class attributes for django_filters.Filter instances (declared filters)
|
|
208
|
-
2. Check Meta.fields (both dict and list formats)
|
|
209
|
-
3. Use Django's internal declared_filters and base_filters as fallback
|
|
210
|
-
4. Handle edge cases and inheritance
|
|
211
|
-
"""
|
|
212
|
-
if not filterset_class:
|
|
213
|
-
return []
|
|
214
|
-
|
|
215
|
-
fields = []
|
|
216
|
-
|
|
217
|
-
# Strategy 1: Check class attributes for Filter instances
|
|
218
|
-
fields.extend(_extract_filterset_fields_from_class_attributes(filterset_class))
|
|
219
|
-
|
|
220
|
-
# Strategy 2: Check Meta.fields (handles both dict and list formats)
|
|
221
|
-
fields.extend(_extract_filterset_fields_from_meta(filterset_class))
|
|
222
|
-
|
|
223
|
-
# Strategy 3: Use Django's internal FilterSet attributes as fallback
|
|
224
|
-
fields.extend(_extract_filterset_fields_from_internal_attrs(filterset_class))
|
|
225
|
-
|
|
226
|
-
# Strategy 4: Try get_fields() method if available (for dynamic filters)
|
|
227
|
-
fields.extend(_extract_filterset_fields_from_get_fields(filterset_class))
|
|
228
|
-
|
|
229
|
-
return sorted(fields)
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Query parameter extraction utilities for Django views."""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from drf_to_mkdoc.utils.common import extract_viewset_from_operation_id
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_query_parameters_from_view(operation_id: str) -> dict[str, Any]:
|
|
11
|
+
"""Extract query parameters from a Django view class"""
|
|
12
|
+
view_class = extract_viewset_from_operation_id(operation_id)
|
|
13
|
+
if not view_class:
|
|
14
|
+
return {
|
|
15
|
+
"search_fields": [],
|
|
16
|
+
"filter_fields": [],
|
|
17
|
+
"ordering_fields": [],
|
|
18
|
+
"filter_backends": [],
|
|
19
|
+
"pagination_fields": [],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
"search_fields": extract_query_parameters_from_view_search_fields(view_class),
|
|
24
|
+
"filter_fields": extract_query_parameters_from_view_filter_fields(view_class),
|
|
25
|
+
"ordering_fields": extract_query_parameters_from_view_ordering_fields(view_class),
|
|
26
|
+
"filter_backends": extract_query_parameters_from_view_filter_backends(view_class),
|
|
27
|
+
"pagination_fields": extract_query_parameters_from_view_pagination_fields(view_class),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def extract_query_parameters_from_view_search_fields(view_class: Any) -> list[str]:
|
|
32
|
+
"""Extract search fields from a Django view class"""
|
|
33
|
+
if not view_class:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
search_fields = []
|
|
37
|
+
if hasattr(view_class, "search_fields") and view_class.search_fields:
|
|
38
|
+
search_fields = sorted(view_class.search_fields)
|
|
39
|
+
|
|
40
|
+
return search_fields
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def extract_query_parameters_from_view_filter_fields(view_class: Any) -> list[str]:
|
|
44
|
+
"""Extract filter fields from a Django view class"""
|
|
45
|
+
if not view_class:
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
filter_fields = []
|
|
49
|
+
if hasattr(view_class, "filterset_class"):
|
|
50
|
+
filter_fields = extract_filterset_fields(view_class.filterset_class)
|
|
51
|
+
elif hasattr(view_class, "filterset_fields") and view_class.filterset_fields:
|
|
52
|
+
filter_fields = sorted(view_class.filterset_fields)
|
|
53
|
+
|
|
54
|
+
return filter_fields
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def extract_query_parameters_from_view_ordering_fields(view_class: Any) -> list[str]:
|
|
58
|
+
"""Extract ordering fields from a Django view class"""
|
|
59
|
+
if not view_class:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
ordering_fields = []
|
|
63
|
+
if hasattr(view_class, "ordering_fields") and view_class.ordering_fields:
|
|
64
|
+
ordering_fields = sorted(view_class.ordering_fields)
|
|
65
|
+
|
|
66
|
+
return ordering_fields
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def extract_query_parameters_from_view_filter_backends(view_class: Any) -> list[str]:
|
|
70
|
+
"""Extract filter backends from a Django view class"""
|
|
71
|
+
if not view_class:
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
filter_backends = []
|
|
75
|
+
if hasattr(view_class, "filter_backends") and view_class.filter_backends:
|
|
76
|
+
for backend in view_class.filter_backends:
|
|
77
|
+
filter_backends.append(getattr(backend, "__name__", str(backend)))
|
|
78
|
+
|
|
79
|
+
return filter_backends
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def extract_query_parameters_from_view_pagination_fields(view_class: Any) -> list[str]:
|
|
83
|
+
"""Extract pagination fields from a Django view class"""
|
|
84
|
+
if not view_class:
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
pagination_fields = []
|
|
88
|
+
if hasattr(view_class, "pagination_class") and view_class.pagination_class:
|
|
89
|
+
pagination_class = view_class.pagination_class
|
|
90
|
+
if hasattr(pagination_class, "get_schema_fields"):
|
|
91
|
+
try:
|
|
92
|
+
# Get pagination fields from the pagination class
|
|
93
|
+
schema_fields = pagination_class().get_schema_fields(view_class())
|
|
94
|
+
pagination_fields = sorted([field.name for field in schema_fields])
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Check if it's specifically the coreapi missing error
|
|
97
|
+
if "coreapi must be installed" in str(e):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"coreapi is required for pagination schema extraction. "
|
|
100
|
+
"Install it with: pip install coreapi"
|
|
101
|
+
) from e
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"Failed to get schema fields from pagination class "
|
|
104
|
+
f"{pagination_class.__name__}: {e}"
|
|
105
|
+
) from e
|
|
106
|
+
else:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"Pagination class {pagination_class.__name__} "
|
|
109
|
+
"must implement get_schema_fields() method"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return pagination_fields
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _extract_filterset_fields_from_class_attributes(filterset_class: Any) -> list[str]:
|
|
116
|
+
fields = []
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
import django_filters
|
|
120
|
+
|
|
121
|
+
# Get all class attributes, including inherited ones
|
|
122
|
+
for attr_name in dir(filterset_class):
|
|
123
|
+
# Skip private attributes and known non-filter attributes
|
|
124
|
+
if attr_name.startswith("_") or attr_name in [
|
|
125
|
+
"Meta",
|
|
126
|
+
"form",
|
|
127
|
+
"queryset",
|
|
128
|
+
"request",
|
|
129
|
+
"errors",
|
|
130
|
+
"qs",
|
|
131
|
+
"is_valid",
|
|
132
|
+
]:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
attr = getattr(filterset_class, attr_name)
|
|
137
|
+
if isinstance(attr, django_filters.Filter):
|
|
138
|
+
if attr_name not in fields:
|
|
139
|
+
fields.append(attr_name)
|
|
140
|
+
except (AttributeError, TypeError):
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
except ImportError:
|
|
144
|
+
# django_filters not available, skip this strategy
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
return fields
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _extract_filterset_fields_from_meta(filterset_class: Any) -> list[str]:
|
|
151
|
+
fields = []
|
|
152
|
+
|
|
153
|
+
if hasattr(filterset_class, "Meta") and hasattr(filterset_class.Meta, "fields"):
|
|
154
|
+
meta_fields = filterset_class.Meta.fields
|
|
155
|
+
if isinstance(meta_fields, list | tuple):
|
|
156
|
+
# List/tuple format: ['field1', 'field2']
|
|
157
|
+
for field in meta_fields:
|
|
158
|
+
if field not in fields:
|
|
159
|
+
fields.append(field)
|
|
160
|
+
elif isinstance(meta_fields, dict):
|
|
161
|
+
# Dictionary format: {'field1': ['exact'], 'field2': ['icontains']}
|
|
162
|
+
for field in meta_fields:
|
|
163
|
+
if field not in fields:
|
|
164
|
+
fields.append(field)
|
|
165
|
+
|
|
166
|
+
return fields
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _extract_filterset_fields_from_internal_attrs(filterset_class: Any) -> list[str]:
|
|
170
|
+
fields = []
|
|
171
|
+
|
|
172
|
+
# Use Django's internal FilterSet attributes as fallback
|
|
173
|
+
# This handles cases where the above strategies might miss some filters
|
|
174
|
+
for internal_attr in ["declared_filters", "base_filters"]:
|
|
175
|
+
if hasattr(filterset_class, internal_attr):
|
|
176
|
+
try:
|
|
177
|
+
internal_filters = getattr(filterset_class, internal_attr)
|
|
178
|
+
if hasattr(internal_filters, "keys"):
|
|
179
|
+
for field in internal_filters:
|
|
180
|
+
if field not in fields:
|
|
181
|
+
fields.append(field)
|
|
182
|
+
except (AttributeError, TypeError):
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
return fields
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _extract_filterset_fields_from_get_fields(filterset_class: Any) -> list[str]:
|
|
189
|
+
fields = []
|
|
190
|
+
|
|
191
|
+
# Try get_fields() method if available (for dynamic filters)
|
|
192
|
+
if hasattr(filterset_class, "get_fields"):
|
|
193
|
+
filterset_instance = filterset_class()
|
|
194
|
+
filterset_fields = filterset_instance.get_fields()
|
|
195
|
+
if filterset_fields and hasattr(filterset_fields, "keys"):
|
|
196
|
+
for field in filterset_fields:
|
|
197
|
+
if field not in fields:
|
|
198
|
+
fields.append(field)
|
|
199
|
+
|
|
200
|
+
return fields
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def extract_filterset_fields(filterset_class: Any) -> list[str]:
|
|
204
|
+
"""Extract field names from a Django FilterSet class
|
|
205
|
+
|
|
206
|
+
This function uses multiple strategies to comprehensively detect all filter fields:
|
|
207
|
+
1. Check class attributes for django_filters.Filter instances (declared filters)
|
|
208
|
+
2. Check Meta.fields (both dict and list formats)
|
|
209
|
+
3. Use Django's internal declared_filters and base_filters as fallback
|
|
210
|
+
4. Handle edge cases and inheritance
|
|
211
|
+
"""
|
|
212
|
+
if not filterset_class:
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
fields = []
|
|
216
|
+
|
|
217
|
+
# Strategy 1: Check class attributes for Filter instances
|
|
218
|
+
fields.extend(_extract_filterset_fields_from_class_attributes(filterset_class))
|
|
219
|
+
|
|
220
|
+
# Strategy 2: Check Meta.fields (handles both dict and list formats)
|
|
221
|
+
fields.extend(_extract_filterset_fields_from_meta(filterset_class))
|
|
222
|
+
|
|
223
|
+
# Strategy 3: Use Django's internal FilterSet attributes as fallback
|
|
224
|
+
fields.extend(_extract_filterset_fields_from_internal_attrs(filterset_class))
|
|
225
|
+
|
|
226
|
+
# Strategy 4: Try get_fields() method if available (for dynamic filters)
|
|
227
|
+
fields.extend(_extract_filterset_fields_from_get_fields(filterset_class))
|
|
228
|
+
|
|
229
|
+
return sorted(fields)
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def _md_query_parameters_format_error_section(query_params: dict[str, Any]) -> str:
|
|
5
|
-
if "error" in query_params:
|
|
6
|
-
return f"**Error:** {query_params['error']}\n\n"
|
|
7
|
-
return ""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _md_query_parameters_format_search_section(query_params: dict[str, Any]) -> str:
|
|
11
|
-
search_fields = query_params.get("search_fields", [])
|
|
12
|
-
if search_fields:
|
|
13
|
-
content = "### Search Parameters\n\n"
|
|
14
|
-
for field in search_fields:
|
|
15
|
-
content += f"- `{field}`\n"
|
|
16
|
-
content += "\n"
|
|
17
|
-
return content
|
|
18
|
-
return ""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _md_query_parameters_format_filter_section(query_params: dict[str, Any]) -> str:
|
|
22
|
-
filter_fields = query_params.get("filter_fields", [])
|
|
23
|
-
if filter_fields:
|
|
24
|
-
content = "### Filter Parameters\n\n"
|
|
25
|
-
for field in filter_fields:
|
|
26
|
-
content += f"- `{field}`\n"
|
|
27
|
-
content += "\n"
|
|
28
|
-
return content
|
|
29
|
-
return ""
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _md_query_parameters_format_ordering_section(query_params: dict[str, Any]) -> str:
|
|
33
|
-
ordering_fields = query_params.get("ordering_fields", [])
|
|
34
|
-
if ordering_fields:
|
|
35
|
-
content = "### Ordering Parameters\n\n"
|
|
36
|
-
for field in ordering_fields:
|
|
37
|
-
content += f"- `{field}`\n"
|
|
38
|
-
content += "\n"
|
|
39
|
-
return content
|
|
40
|
-
return ""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _md_query_parameters_format_pagination_section(query_params: dict[str, Any]) -> str:
|
|
44
|
-
pagination_fields = query_params.get("pagination_fields", [])
|
|
45
|
-
if pagination_fields:
|
|
46
|
-
content = "### Pagination Parameters\n\n"
|
|
47
|
-
for field in pagination_fields:
|
|
48
|
-
content += f"- `{field}`\n"
|
|
49
|
-
content += "\n"
|
|
50
|
-
return content
|
|
51
|
-
return ""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _md_query_parameters_format_filter_backends_section(query_params: dict[str, Any]) -> str:
|
|
55
|
-
filter_backends = query_params.get("filter_backends", [])
|
|
56
|
-
if filter_backends:
|
|
57
|
-
return f"**Filter Backends:** {', '.join(filter_backends)}\n\n"
|
|
58
|
-
return ""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def generate_query_parameters_md(query_params: dict[str, Any]) -> str:
|
|
62
|
-
"""Format query parameters into markdown documentation"""
|
|
63
|
-
if "error" in query_params:
|
|
64
|
-
return _md_query_parameters_format_error_section(query_params)
|
|
65
|
-
|
|
66
|
-
content = ""
|
|
67
|
-
content += _md_query_parameters_format_search_section(query_params)
|
|
68
|
-
content += _md_query_parameters_format_filter_section(query_params)
|
|
69
|
-
content += _md_query_parameters_format_ordering_section(query_params)
|
|
70
|
-
content += _md_query_parameters_format_pagination_section(query_params)
|
|
71
|
-
content += _md_query_parameters_format_filter_backends_section(query_params)
|
|
72
|
-
return content
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _md_query_parameters_format_error_section(query_params: dict[str, Any]) -> str:
|
|
5
|
+
if "error" in query_params:
|
|
6
|
+
return f"**Error:** {query_params['error']}\n\n"
|
|
7
|
+
return ""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _md_query_parameters_format_search_section(query_params: dict[str, Any]) -> str:
|
|
11
|
+
search_fields = query_params.get("search_fields", [])
|
|
12
|
+
if search_fields:
|
|
13
|
+
content = "### Search Parameters\n\n"
|
|
14
|
+
for field in search_fields:
|
|
15
|
+
content += f"- `{field}`\n"
|
|
16
|
+
content += "\n"
|
|
17
|
+
return content
|
|
18
|
+
return ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _md_query_parameters_format_filter_section(query_params: dict[str, Any]) -> str:
|
|
22
|
+
filter_fields = query_params.get("filter_fields", [])
|
|
23
|
+
if filter_fields:
|
|
24
|
+
content = "### Filter Parameters\n\n"
|
|
25
|
+
for field in filter_fields:
|
|
26
|
+
content += f"- `{field}`\n"
|
|
27
|
+
content += "\n"
|
|
28
|
+
return content
|
|
29
|
+
return ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _md_query_parameters_format_ordering_section(query_params: dict[str, Any]) -> str:
|
|
33
|
+
ordering_fields = query_params.get("ordering_fields", [])
|
|
34
|
+
if ordering_fields:
|
|
35
|
+
content = "### Ordering Parameters\n\n"
|
|
36
|
+
for field in ordering_fields:
|
|
37
|
+
content += f"- `{field}`\n"
|
|
38
|
+
content += "\n"
|
|
39
|
+
return content
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _md_query_parameters_format_pagination_section(query_params: dict[str, Any]) -> str:
|
|
44
|
+
pagination_fields = query_params.get("pagination_fields", [])
|
|
45
|
+
if pagination_fields:
|
|
46
|
+
content = "### Pagination Parameters\n\n"
|
|
47
|
+
for field in pagination_fields:
|
|
48
|
+
content += f"- `{field}`\n"
|
|
49
|
+
content += "\n"
|
|
50
|
+
return content
|
|
51
|
+
return ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _md_query_parameters_format_filter_backends_section(query_params: dict[str, Any]) -> str:
|
|
55
|
+
filter_backends = query_params.get("filter_backends", [])
|
|
56
|
+
if filter_backends:
|
|
57
|
+
return f"**Filter Backends:** {', '.join(filter_backends)}\n\n"
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def generate_query_parameters_md(query_params: dict[str, Any]) -> str:
|
|
62
|
+
"""Format query parameters into markdown documentation"""
|
|
63
|
+
if "error" in query_params:
|
|
64
|
+
return _md_query_parameters_format_error_section(query_params)
|
|
65
|
+
|
|
66
|
+
content = ""
|
|
67
|
+
content += _md_query_parameters_format_search_section(query_params)
|
|
68
|
+
content += _md_query_parameters_format_filter_section(query_params)
|
|
69
|
+
content += _md_query_parameters_format_ordering_section(query_params)
|
|
70
|
+
content += _md_query_parameters_format_pagination_section(query_params)
|
|
71
|
+
content += _md_query_parameters_format_filter_backends_section(query_params)
|
|
72
|
+
return content
|