1
1
import warnings
2
+ from enum import Enum
2
3
from operator import attrgetter
3
4
from urllib .parse import urljoin
4
5
@@ -37,16 +38,21 @@ def get_schema(self, request=None, public=False):
37
38
Generate a OpenAPI schema.
38
39
"""
39
40
self ._initialise_endpoints ()
41
+ components_schemas = {}
40
42
41
43
# Iterate endpoints generating per method path operations.
42
- # TODO: …and reference components.
43
44
paths = {}
44
45
_ , view_endpoints = self ._get_paths_and_endpoints (None if public else request )
45
46
for path , method , view in view_endpoints :
46
47
if not self .has_view_permissions (path , method , view ):
47
48
continue
48
49
49
50
operation = view .schema .get_operation (path , method )
51
+ component = view .schema .get_components (path , method )
52
+
53
+ if component is not None :
54
+ components_schemas .update (component )
55
+
50
56
# Normalise path for any provided mount url.
51
57
if path .startswith ('/' ):
52
58
path = path [1 :]
@@ -59,9 +65,14 @@ def get_schema(self, request=None, public=False):
59
65
schema = {
60
66
'openapi' : '3.0.2' ,
61
67
'info' : self .get_info (),
62
- 'paths' : paths ,
68
+ 'paths' : paths
63
69
}
64
70
71
+ if len (components_schemas ) > 0 :
72
+ schema ['components' ] = {
73
+ 'schemas' : components_schemas
74
+ }
75
+
65
76
return schema
66
77
67
78
# View Inspectors
@@ -99,6 +110,21 @@ def get_operation(self, path, method):
99
110
100
111
return operation
101
112
113
+ def get_components (self , path , method ):
114
+ serializer = self ._get_serializer (path , method )
115
+
116
+ if not isinstance (serializer , serializers .Serializer ):
117
+ return None
118
+
119
+ # If the model has no model, then the serializer will be inlined
120
+ if not hasattr (serializer , 'Meta' ) or not hasattr (serializer .Meta , 'model' ):
121
+ return None
122
+
123
+ model_name = serializer .Meta .model .__name__
124
+ content = self ._map_serializer (serializer )
125
+
126
+ return {model_name : content }
127
+
102
128
def _get_operation_id (self , path , method ):
103
129
"""
104
130
Compute an operation ID from the model, serializer or view name.
@@ -470,6 +496,10 @@ def _get_serializer(self, method, path):
470
496
.format (view .__class__ .__name__ , method , path ))
471
497
return None
472
498
499
+ def _get_reference (self , serializer ):
500
+ model_name = serializer .Meta .model .__name__
501
+ return {'$ref' : '#/components/schemas/{}' .format (model_name )}
502
+
473
503
def _get_request_body (self , path , method ):
474
504
if method not in ('PUT' , 'PATCH' , 'POST' ):
475
505
return {}
@@ -479,20 +509,30 @@ def _get_request_body(self, path, method):
479
509
serializer = self ._get_serializer (path , method )
480
510
481
511
if not isinstance (serializer , serializers .Serializer ):
482
- return {}
483
-
484
- content = self ._map_serializer (serializer )
485
- # No required fields for PATCH
486
- if method == 'PATCH' :
487
- content .pop ('required' , None )
488
- # No read_only fields for request.
489
- for name , schema in content ['properties' ].copy ().items ():
490
- if 'readOnly' in schema :
491
- del content ['properties' ][name ]
512
+ item_schema = {}
513
+ elif hasattr (serializer , 'Meta' ) and hasattr (serializer .Meta , 'model' ):
514
+ # If the serializer uses a model, we should use a reference
515
+ item_schema = self ._get_reference (serializer )
516
+ else :
517
+ # There is no model, we'll map the serializer's fields
518
+ item_schema = self ._map_serializer (serializer )
519
+ # No required fields for PATCH
520
+ if method == 'PATCH' :
521
+ item_schema .pop ('required' , None )
522
+ # No read_only fields for request.
523
+ # No write_only fields for response.
524
+ for name , schema in item_schema ['properties' ].copy ().items ():
525
+ if 'writeOnly' in schema :
526
+ del item_schema ['properties' ][name ]
527
+ if 'required' in item_schema :
528
+ item_schema ['required' ] = [f for f in item_schema ['required' ] if f != name ]
529
+ for name , schema in item_schema ['properties' ].copy ().items ():
530
+ if 'readOnly' in schema :
531
+ del item_schema ['properties' ][name ]
492
532
493
533
return {
494
534
'content' : {
495
- ct : {'schema' : content }
535
+ ct : {'schema' : item_schema }
496
536
for ct in self .request_media_types
497
537
}
498
538
}
@@ -508,10 +548,15 @@ def _get_responses(self, path, method):
508
548
509
549
self .response_media_types = self .map_renderers (path , method )
510
550
511
- item_schema = {}
512
551
serializer = self ._get_serializer (path , method )
513
552
514
- if isinstance (serializer , serializers .Serializer ):
553
+ if not isinstance (serializer , serializers .Serializer ):
554
+ item_schema = {}
555
+ elif hasattr (serializer , 'Meta' ) and hasattr (serializer .Meta , 'model' ):
556
+ # If the serializer uses a model, we should use a reference
557
+ item_schema = self ._get_reference (serializer )
558
+ else :
559
+ # There is no model, we'll map the serializer's fields
515
560
item_schema = self ._map_serializer (serializer )
516
561
# No write_only fields for response.
517
562
for name , schema in item_schema ['properties' ].copy ().items ():
0 commit comments