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.
@@ -464,6 +490,10 @@ def _get_serializer(self, path, method):
464
490
.format (view .__class__ .__name__ , method , path ))
465
491
return None
466
492
493
+ def _get_reference (self , serializer ):
494
+ model_name = serializer .Meta .model .__name__
495
+ return {'$ref' : '#/components/schemas/{}' .format (model_name )}
496
+
467
497
def _get_request_body (self , path , method ):
468
498
if method not in ('PUT' , 'PATCH' , 'POST' ):
469
499
return {}
@@ -473,20 +503,30 @@ def _get_request_body(self, path, method):
473
503
serializer = self ._get_serializer (path , method )
474
504
475
505
if not isinstance (serializer , serializers .Serializer ):
476
- return {}
477
-
478
- content = self ._map_serializer (serializer )
479
- # No required fields for PATCH
480
- if method == 'PATCH' :
481
- content .pop ('required' , None )
482
- # No read_only fields for request.
483
- for name , schema in content ['properties' ].copy ().items ():
484
- if 'readOnly' in schema :
485
- del content ['properties' ][name ]
506
+ item_schema = {}
507
+ elif hasattr (serializer , 'Meta' ) and hasattr (serializer .Meta , 'model' ):
508
+ # If the serializer uses a model, we should use a reference
509
+ item_schema = self ._get_reference (serializer )
510
+ else :
511
+ # There is no model, we'll map the serializer's fields
512
+ item_schema = self ._map_serializer (serializer )
513
+ # No required fields for PATCH
514
+ if method == 'PATCH' :
515
+ item_schema .pop ('required' , None )
516
+ # No read_only fields for request.
517
+ # No write_only fields for response.
518
+ for name , schema in item_schema ['properties' ].copy ().items ():
519
+ if 'writeOnly' in schema :
520
+ del item_schema ['properties' ][name ]
521
+ if 'required' in item_schema :
522
+ item_schema ['required' ] = [f for f in item_schema ['required' ] if f != name ]
523
+ for name , schema in item_schema ['properties' ].copy ().items ():
524
+ if 'readOnly' in schema :
525
+ del item_schema ['properties' ][name ]
486
526
487
527
return {
488
528
'content' : {
489
- ct : {'schema' : content }
529
+ ct : {'schema' : item_schema }
490
530
for ct in self .request_media_types
491
531
}
492
532
}
@@ -502,10 +542,15 @@ def _get_responses(self, path, method):
502
542
503
543
self .response_media_types = self .map_renderers (path , method )
504
544
505
- item_schema = {}
506
545
serializer = self ._get_serializer (path , method )
507
546
508
- if isinstance (serializer , serializers .Serializer ):
547
+ if not isinstance (serializer , serializers .Serializer ):
548
+ item_schema = {}
549
+ elif hasattr (serializer , 'Meta' ) and hasattr (serializer .Meta , 'model' ):
550
+ # If the serializer uses a model, we should use a reference
551
+ item_schema = self ._get_reference (serializer )
552
+ else :
553
+ # There is no model, we'll map the serializer's fields
509
554
item_schema = self ._map_serializer (serializer )
510
555
# No write_only fields for response.
511
556
for name , schema in item_schema ['properties' ].copy ().items ():
0 commit comments