44import uuid
55from abc import ABCMeta
66from collections import ChainMap
7- from typing import Any , List , Match , Optional , Tuple , Type , Union
7+ from typing import Any , Match , Optional , Tuple , Type
88
9- from django .db import models
109from django .http import HttpRequest
1110from ninja import ModelSchema
1211from ninja_extra import ControllerBase , http_delete , http_get , http_patch , http_put
1312from ninja_extra .pagination import paginate
1413
14+ from easy .controller .meta_conf import MODEL_FIELDS_ATTR_DEFAULT , ModelOptions
1515from easy .domain .orm import CrudModel
1616from easy .response import BaseApiResponse
1717from easy .services import BaseService
2020logger = logging .getLogger (__name__ )
2121
2222
23- class APIControllerBase (ControllerBase ):
24- """Reserved for customization"""
25-
26- ...
27-
28-
2923class CrudAPI (CrudModel ):
3024 # Never add type note to service, it will cause injection error
3125 def __init__ (self , service = None ): # type: ignore
3226 # Critical to set __Meta
3327 self .service = service
3428 if self .service :
3529 self .model = self .service .model
36- _meta = getattr (self , "Meta" , None )
37- if self .model and _meta :
38- setattr (
39- self .model ,
40- "__Meta" ,
41- {
42- "generate_crud" : getattr (_meta , "generate_crud" , True ),
43- "model_exclude" : getattr (_meta , "model_exclude" , None ),
44- "model_fields" : getattr (_meta , "model_fields" , "__all__" ),
45- "model_recursive" : getattr (_meta , "model_recursive" , False ),
46- "model_join" : getattr (_meta , "model_join" , True ),
47- "sensitive_fields" : getattr (
48- _meta , "model_sensitive_fields" , ["password" , "token" ]
49- ),
50- },
51- )
30+
31+ _model_opts : ModelOptions = ModelOptions .get_model_options (self .__class__ )
32+ if self .model and _model_opts :
33+ ModelOptions .set_model_meta (self .model , _model_opts )
34+
5235 if not service :
5336 self .service = BaseService (model = self .model )
5437 super ().__init__ (model = self .model )
5538
56- # Define Controller APIs for auto generation
57- async def get_obj (self , request : HttpRequest , id : int ) -> Any :
58- """
59- GET /{id}
60- Retrieve a single Object
61- """
62- try :
63- qs = await self .service .get_obj (id )
64- except Exception as e : # pragma: no cover
65- logger .error (f"Get Error - { e } " , exc_info = True )
66- return BaseApiResponse (str (e ), message = "Get Failed" , errno = 500 )
67- if qs :
68- return qs
69- else :
70- return BaseApiResponse (message = "Not Found" , errno = 404 )
71-
72- async def del_obj (self , request : HttpRequest , id : int ) -> Any :
73- """
74- DELETE /{id}
75- Delete a single Object
76- """
77- if await self .service .del_obj (id ):
78- return BaseApiResponse ("Deleted." , errno = 204 )
79- else :
80- return BaseApiResponse ("Not Found." , errno = 404 )
81-
82- @paginate
83- async def get_objs (self , request : HttpRequest , filters : str = None ) -> Any :
84- """
85- GET /?maximum={int}&filters={filters_dict}
86- Retrieve multiple Object (optional: maximum # and filters)
87- """
88- if filters :
89- return await self .service .get_objs (** json .loads (filters ))
90- return await self .service .get_objs ()
91-
9239
9340class CrudApiMetaclass (ABCMeta ):
9441 def __new__ (mcs , name : str , bases : Tuple [Type [Any ], ...], attrs : dict ) -> Any :
95- # Get configs from Meta
96- temp_cls : Type = super ().__new__ (mcs , name , (object ,), attrs )
97- temp_opts : ModelOptions = ModelOptions (getattr (temp_cls , "Meta" , None ))
98- opts_model : Optional [Type [models .Model ]] = temp_opts .model
99- opts_generate_crud : Optional [bool ] = temp_opts .generate_crud
100- opts_fields_exclude : Optional [str ] = temp_opts .model_exclude
101- opts_fields : Optional [str ] = temp_opts .model_fields
102- opts_recursive : Optional [bool ] = temp_opts .model_recursive
103- opts_join : Optional [bool ] = temp_opts .model_join
104- opts_sensitive_fields : Optional [
105- Union [str , List [str ]]
106- ] = temp_opts .sensitive_fields
107-
10842 def is_private_attrs (attr_name : str ) -> Optional [Match [str ]]:
10943 return re .match (r"^__[^\d\W]\w*\Z__$" , attr_name , re .UNICODE )
11044
@@ -117,32 +51,76 @@ def is_private_attrs(attr_name: str) -> Optional[Match[str]]:
11751 )
11852 base_cls_attrs : dict = {}
11953 base_cls_attrs .update (parent_attrs )
120- if opts_generate_crud :
54+
55+ # Get configs from Meta
56+ _temp_cls : Type = super ().__new__ (mcs , name , (object ,), base_cls_attrs )
57+ model_opts : ModelOptions = ModelOptions .get_model_options (_temp_cls )
58+
59+ # Define Controller APIs for auto generation
60+ async def get_obj (self , request : HttpRequest , id : int ) -> Any : # type: ignore
61+ """
62+ GET /{id}
63+ Retrieve a single Object
64+ """
65+ try :
66+ qs = await self .service .get_obj (id )
67+ except Exception as e : # pragma: no cover
68+ logger .error (f"Get Error - { e } " , exc_info = True )
69+ return BaseApiResponse (str (e ), message = "Get Failed" , errno = 500 )
70+ if qs :
71+ return qs
72+ else :
73+ return BaseApiResponse (message = "Not Found" , errno = 404 )
74+
75+ async def del_obj (self , request : HttpRequest , id : int ) -> Any : # type: ignore
76+ """
77+ DELETE /{id}
78+ Delete a single Object
79+ """
80+ if await self .service .del_obj (id ):
81+ return BaseApiResponse ("Deleted." , errno = 204 )
82+ else :
83+ return BaseApiResponse ("Not Found." , errno = 404 )
84+
85+ @paginate
86+ async def get_objs (self , request : HttpRequest , filters : str = None ) -> Any : # type: ignore
87+ """
88+ GET /?maximum={int}&filters={filters_dict}
89+ Retrieve multiple Object (optional: maximum # and filters)
90+ """
91+ if filters :
92+ return await self .service .get_objs (** json .loads (filters ))
93+ return await self .service .get_objs ()
94+
95+ if model_opts .generate_crud and model_opts .model :
12196 base_cls_attrs .update (
12297 {
12398 "get_obj" : http_get ("/{id}" , summary = "Get a single object" )(
124- copy_func (CrudAPI . get_obj ) # type: ignore
99+ copy_func (get_obj ) # type: ignore
125100 ),
126101 "del_obj" : http_delete ("/{id}" , summary = "Delete a single object" )(
127- copy_func (CrudAPI . del_obj ) # type: ignore
102+ copy_func (del_obj ) # type: ignore
128103 ),
129104 "get_objs" : http_get ("/" , summary = "Get multiple objects" )(
130- copy_func (CrudAPI . get_objs ) # type: ignore
105+ copy_func (get_objs ) # type: ignore
131106 ),
132107 }
133108 )
134- if opts_generate_crud and opts_model :
135109
136110 class DataSchema (ModelSchema ):
137111 class Config :
138- model = opts_model
139- if opts_fields_exclude :
140- model_exclude = opts_fields_exclude
112+ model = model_opts . model
113+ if model_opts . model_exclude :
114+ model_exclude = model_opts . model_exclude
141115 else :
142- if opts_fields == "__all__" :
143- model_fields = "__all__"
116+ if model_opts . model_fields == MODEL_FIELDS_ATTR_DEFAULT :
117+ model_fields = MODEL_FIELDS_ATTR_DEFAULT
144118 else :
145- model_fields = opts_fields if opts_fields else "__all__"
119+ model_fields = (
120+ model_opts .model_fields # type: ignore
121+ if model_opts .model_fields
122+ else MODEL_FIELDS_ATTR_DEFAULT
123+ )
146124
147125 async def add_obj ( # type: ignore
148126 self , request : HttpRequest , data : DataSchema
@@ -170,19 +148,16 @@ async def patch_obj( # type: ignore
170148 return BaseApiResponse ("Update Failed" , errno = 400 )
171149
172150 DataSchema .__name__ = (
173- f"{ opts_model .__name__ } __AutoSchema({ str (uuid .uuid4 ())[:4 ]} )"
151+ f"{ model_opts . model .__name__ } __AutoSchema({ str (uuid .uuid4 ())[:4 ]} )"
174152 )
175153
176- setattr (CrudAPI , "patch_obj" , patch_obj )
177- setattr (CrudAPI , "add_obj" , add_obj )
178-
179154 base_cls_attrs .update (
180155 {
181156 "patch_obj" : http_patch ("/{id}" , summary = "Patch a single object" )(
182- copy_func (CrudAPI . patch_obj ) # type: ignore
157+ copy_func (patch_obj ) # type: ignore
183158 ),
184159 "add_obj" : http_put ("/" , summary = "Create" )(
185- copy_func (CrudAPI . add_obj ) # type: ignore
160+ copy_func (add_obj ) # type: ignore
186161 ),
187162 }
188163 )
@@ -191,49 +166,14 @@ async def patch_obj( # type: ignore
191166 mcs ,
192167 name ,
193168 (
194- APIControllerBase ,
169+ ControllerBase ,
195170 CrudAPI ,
196171 ),
197172 base_cls_attrs ,
198173 )
199174
200- if opts_model :
201- setattr (
202- opts_model ,
203- "__Meta" ,
204- {
205- "generate_crud" : opts_generate_crud ,
206- "model_exclude" : opts_fields_exclude ,
207- "model_fields" : opts_fields ,
208- "model_recursive" : opts_recursive ,
209- "model_join" : opts_join ,
210- "sensitive_fields" : opts_sensitive_fields ,
211- },
212- )
213- setattr (new_cls , "model" , opts_model )
175+ if model_opts .model :
176+ ModelOptions .set_model_meta (model_opts .model , model_opts )
177+ setattr (new_cls , "model" , model_opts .model )
214178
215179 return new_cls
216-
217-
218- class ModelOptions :
219- def __init__ (self , options : object = None ):
220- """
221- Configuration reader
222- """
223- self .model : Optional [Type [models .Model ]] = getattr (options , "model" , None )
224- self .generate_crud : Optional [Union [bool ]] = getattr (
225- options , "generate_crud" , True
226- )
227- self .model_exclude : Optional [Union [str ]] = getattr (
228- options , "model_exclude" , None
229- )
230- self .model_fields : Optional [Union [str ]] = getattr (
231- options , "model_fields" , "__all__"
232- )
233- self .model_join : Optional [Union [bool ]] = getattr (options , "model_join" , True )
234- self .model_recursive : Optional [Union [bool ]] = getattr (
235- options , "model_recursive" , False
236- )
237- self .sensitive_fields : Optional [Union [str , List [str ]]] = getattr (
238- options , "sensitive_fields" , ["token" , "password" ]
239- )
0 commit comments