Skip to content

Commit 10b08b0

Browse files
committed
refactor: model options
1 parent b567064 commit 10b08b0

File tree

3 files changed

+171
-134
lines changed

3 files changed

+171
-134
lines changed

easy/controller/meta.py

Lines changed: 24 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import uuid
55
from abc import ABCMeta
66
from 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
109
from django.http import HttpRequest
1110
from ninja import ModelSchema
1211
from ninja_extra import ControllerBase, http_delete, http_get, http_patch, http_put
1312
from ninja_extra.pagination import paginate
1413

14+
from easy.controller.meta_conf import ModelOptions
1515
from easy.domain.orm import CrudModel
1616
from easy.response import BaseApiResponse
1717
from easy.services import BaseService
@@ -20,35 +20,18 @@
2020
logger = logging.getLogger(__name__)
2121

2222

23-
class APIControllerBase(ControllerBase):
24-
"""Reserved for customization"""
25-
26-
...
27-
28-
2923
class 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)
@@ -70,17 +53,8 @@ def is_private_attrs(attr_name: str) -> Optional[Match[str]]:
7053
base_cls_attrs.update(parent_attrs)
7154

7255
# Get configs from Meta
73-
temp_cls: Type = super().__new__(mcs, name, (object,), base_cls_attrs)
74-
temp_opts: ModelOptions = ModelOptions(getattr(temp_cls, "Meta", None))
75-
opts_model: Optional[Type[models.Model]] = temp_opts.model
76-
opts_generate_crud: Optional[bool] = temp_opts.generate_crud
77-
opts_fields_exclude: Optional[str] = temp_opts.model_exclude
78-
opts_fields: Optional[str] = temp_opts.model_fields
79-
opts_recursive: Optional[bool] = temp_opts.model_recursive
80-
opts_join: Optional[bool] = temp_opts.model_join
81-
opts_sensitive_fields: Optional[
82-
Union[str, List[str]]
83-
] = temp_opts.sensitive_fields
56+
_temp_cls: Type = super().__new__(mcs, name, (object,), base_cls_attrs)
57+
model_opts: ModelOptions = ModelOptions.get_model_options(_temp_cls)
8458

8559
# Define Controller APIs for auto generation
8660
async def get_obj(self, request: HttpRequest, id: int) -> Any: # type: ignore
@@ -118,7 +92,7 @@ async def get_objs(self, request: HttpRequest, filters: str = None) -> Any: # t
11892
return await self.service.get_objs(**json.loads(filters))
11993
return await self.service.get_objs()
12094

121-
if opts_generate_crud and opts_model:
95+
if model_opts.generate_crud and model_opts.model:
12296
base_cls_attrs.update(
12397
{
12498
"get_obj": http_get("/{id}", summary="Get a single object")(
@@ -135,14 +109,18 @@ async def get_objs(self, request: HttpRequest, filters: str = None) -> Any: # t
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__":
116+
if model_opts.model_fields == "__all__":
143117
model_fields = "__all__"
144118
else:
145-
model_fields = opts_fields if opts_fields else "__all__"
119+
model_fields = (
120+
model_opts.model_fields
121+
if model_opts.model_fields
122+
else "__all__"
123+
)
146124

147125
async def add_obj( # type: ignore
148126
self, request: HttpRequest, data: DataSchema
@@ -170,7 +148,7 @@ 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

176154
base_cls_attrs.update(
@@ -188,49 +166,14 @@ async def patch_obj( # type: ignore
188166
mcs,
189167
name,
190168
(
191-
APIControllerBase,
169+
ControllerBase,
192170
CrudAPI,
193171
),
194172
base_cls_attrs,
195173
)
196174

197-
if opts_model:
198-
setattr(
199-
opts_model,
200-
"__Meta",
201-
{
202-
"generate_crud": opts_generate_crud,
203-
"model_exclude": opts_fields_exclude,
204-
"model_fields": opts_fields,
205-
"model_recursive": opts_recursive,
206-
"model_join": opts_join,
207-
"sensitive_fields": opts_sensitive_fields,
208-
},
209-
)
210-
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)
211178

212179
return new_cls
213-
214-
215-
class ModelOptions:
216-
def __init__(self, options: object = None):
217-
"""
218-
Configuration reader
219-
"""
220-
self.model: Optional[Type[models.Model]] = getattr(options, "model", None)
221-
self.generate_crud: Optional[Union[bool]] = getattr(
222-
options, "generate_crud", True
223-
)
224-
self.model_exclude: Optional[Union[str]] = getattr(
225-
options, "model_exclude", None
226-
)
227-
self.model_fields: Optional[Union[str]] = getattr(
228-
options, "model_fields", "__all__"
229-
)
230-
self.model_join: Optional[Union[bool]] = getattr(options, "model_join", True)
231-
self.model_recursive: Optional[Union[bool]] = getattr(
232-
options, "model_recursive", False
233-
)
234-
self.sensitive_fields: Optional[Union[str, List[str]]] = getattr(
235-
options, "sensitive_fields", ["token", "password"]
236-
)

easy/controller/meta_conf.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
from typing import Any, List, Optional, Type, Union
2+
3+
from django.db import models
4+
5+
META_ATTRIBUTE_NAME: str = "__EASY_API_META__"
6+
7+
GENERATE_CRUD_ATTR: str = "generate_crud"
8+
GENERATE_CRUD_ATTR_DEFAULT = True
9+
10+
MODEL_EXCLUDE_ATTR: str = "model_exclude"
11+
MODEL_EXCLUDE_ATTR_DEFAULT: List = []
12+
13+
MODEL_FIELDS_ATTR: str = "model_fields"
14+
MODEL_FIELDS_ATTR_DEFAULT: str = "__all__"
15+
16+
MODEL_RECURSIVE_ATTR: str = "model_recursive"
17+
MODEL_RECURSIVE_ATTR_DEFAULT: bool = False
18+
19+
MODEL_JOIN_ATTR: str = "model_join"
20+
MODEL_JOIN_ATTR_DEFAULT: bool = False
21+
22+
SENSITIVE_FIELDS_ATTR: str = "sensitive_fields"
23+
SENSITIVE_FIELDS_ATTR_DEFAULT: List = ["password", "token"]
24+
25+
26+
class ModelOptions:
27+
def __init__(self, options: object = None):
28+
"""
29+
Configuration reader
30+
"""
31+
self.model: Optional[Type[models.Model]] = getattr(options, "model", None)
32+
self.generate_crud: Optional[Union[bool]] = getattr(
33+
options, GENERATE_CRUD_ATTR, True
34+
)
35+
self.model_exclude: Optional[Union[str]] = getattr(
36+
options, MODEL_EXCLUDE_ATTR, None
37+
)
38+
self.model_fields: Optional[Union[str]] = getattr(
39+
options, MODEL_FIELDS_ATTR, "__all__"
40+
)
41+
self.model_join: Optional[Union[bool]] = getattr(
42+
options, MODEL_JOIN_ATTR, False
43+
)
44+
self.model_recursive: Optional[Union[bool]] = getattr(
45+
options, MODEL_RECURSIVE_ATTR, False
46+
)
47+
self.sensitive_fields: Optional[Union[str, List[str]]] = getattr(
48+
options, SENSITIVE_FIELDS_ATTR, list(SENSITIVE_FIELDS_ATTR_DEFAULT)
49+
)
50+
51+
@classmethod
52+
def get_model_options(cls, klass: Type) -> Any:
53+
return ModelOptions(getattr(klass, "Meta", None))
54+
55+
@classmethod
56+
def set_model_meta(
57+
cls, model: Type[models.Model], model_opts: "ModelOptions"
58+
) -> None:
59+
setattr(
60+
model,
61+
META_ATTRIBUTE_NAME,
62+
{
63+
GENERATE_CRUD_ATTR: model_opts.generate_crud,
64+
MODEL_EXCLUDE_ATTR: model_opts.model_exclude,
65+
MODEL_FIELDS_ATTR: model_opts.model_fields,
66+
MODEL_RECURSIVE_ATTR: model_opts.model_recursive,
67+
MODEL_JOIN_ATTR: model_opts.model_join,
68+
SENSITIVE_FIELDS_ATTR: model_opts.sensitive_fields,
69+
},
70+
)
71+
72+
73+
class ModelMetaConfig(object):
74+
@staticmethod
75+
def get_configuration(obj: models.Model, _name: str, default: Any = None) -> Any:
76+
_value = default if default else None
77+
if hasattr(obj, META_ATTRIBUTE_NAME):
78+
_value = getattr(obj, META_ATTRIBUTE_NAME).get(_name, _value)
79+
return _value
80+
81+
def get_model_recursive(self, obj: models.Model) -> bool:
82+
model_recursive: bool = self.get_configuration(
83+
obj, MODEL_RECURSIVE_ATTR, default=MODEL_RECURSIVE_ATTR_DEFAULT
84+
)
85+
return model_recursive
86+
87+
def get_model_join(self, obj: models.Model) -> bool:
88+
model_join: bool = self.get_configuration(
89+
obj, MODEL_JOIN_ATTR, default=MODEL_JOIN_ATTR_DEFAULT
90+
)
91+
return model_join
92+
93+
def get_model_fields_list(self, obj: models.Model) -> List[Any]:
94+
model_fields: List = self.get_configuration(
95+
obj, MODEL_FIELDS_ATTR, default=MODEL_FIELDS_ATTR_DEFAULT
96+
)
97+
return model_fields
98+
99+
def get_model_exclude_list(self, obj: models.Model) -> List[Any]:
100+
exclude_list: List = self.get_configuration(
101+
obj, MODEL_EXCLUDE_ATTR, default=MODEL_EXCLUDE_ATTR_DEFAULT
102+
)
103+
return exclude_list
104+
105+
def get_sensitive_list(self, obj: models.Model) -> List[Any]:
106+
sensitive_list: List = self.get_configuration(
107+
obj, SENSITIVE_FIELDS_ATTR, default=SENSITIVE_FIELDS_ATTR_DEFAULT
108+
)
109+
return sensitive_list
110+
111+
def get_final_excluded_list(self, obj: models.Model) -> List[Any]:
112+
total_excluded_list = []
113+
sensitive_list: List = list(SENSITIVE_FIELDS_ATTR_DEFAULT)
114+
excluded_list = []
115+
116+
sensitive_fields = self.get_sensitive_list(obj)
117+
if sensitive_fields:
118+
sensitive_list.extend(sensitive_fields)
119+
sensitive_list = list(set(sensitive_list))
120+
121+
excluded_fields = self.get_model_exclude_list(obj)
122+
if excluded_fields:
123+
excluded_list.extend(excluded_fields)
124+
excluded_list = list(set(excluded_list))
125+
126+
total_excluded_list.extend(sensitive_list)
127+
total_excluded_list.extend(excluded_list)
128+
return list(set(total_excluded_list))
129+
130+
def show_field(self, obj: models.Model, field_name: str) -> bool:
131+
model_exclude_list = self.get_model_exclude_list(obj)
132+
if model_exclude_list:
133+
if field_name in self.get_final_excluded_list(obj):
134+
return False
135+
else:
136+
if field_name in self.get_final_excluded_list(obj):
137+
return False
138+
model_fields_list = self.get_model_fields_list(obj)
139+
if model_fields_list != MODEL_FIELDS_ATTR_DEFAULT:
140+
if field_name not in model_fields_list:
141+
return False
142+
return True

0 commit comments

Comments
 (0)