Skip to content

Commit e73dda9

Browse files
authored
Merge pull request #9 from freemindcore/feat/more-configuration
Feat/more configuration
2 parents 5fb5dce + a18c3f1 commit e73dda9

File tree

9 files changed

+256
-197
lines changed

9 files changed

+256
-197
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.26
2+
current_version = 0.1.29
33
commit = True
44
tag = True
55
parse = (?P<major>\d+)\.(?P<feat>\d+)\.(?P<patch>\d+)

easy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Django Easy API - Easy and Fast Django REST framework based on Django-ninja-extra"""
22

3-
__version__ = "0.1.26"
3+
__version__ = "0.1.29"
44

55
from easy.main import EasyAPI
66

easy/controller/admin_auto_api.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
from ninja_extra.permissions import BasePermission
77

88
from easy.controller.base import CrudAPIController
9+
from easy.controller.meta_conf import (
10+
GENERATE_CRUD_ATTR_DEFAULT,
11+
MODEL_FIELDS_ATTR_DEFAULT,
12+
MODEL_JOIN_ATTR_DEFAULT,
13+
MODEL_RECURSIVE_ATTR_DEFAULT,
14+
SENSITIVE_FIELDS_ATTR_DEFAULT,
15+
)
916
from easy.permissions import AdminSitePermission, BaseApiPermission
1017

1118
logger = logging.getLogger(__name__)
@@ -24,11 +31,11 @@ def create_api_controller(
2431
(object,),
2532
{
2633
"model": model,
27-
"generate_crud": True,
28-
"model_fields": "__all__",
29-
"model_recursive": False,
30-
"model_join": True,
31-
"sensitive_fields": ["password"],
34+
"generate_crud": GENERATE_CRUD_ATTR_DEFAULT,
35+
"model_fields": MODEL_FIELDS_ATTR_DEFAULT,
36+
"model_recursive": MODEL_RECURSIVE_ATTR_DEFAULT,
37+
"model_join": MODEL_JOIN_ATTR_DEFAULT,
38+
"sensitive_fields": SENSITIVE_FIELDS_ATTR_DEFAULT,
3239
},
3340
)
3441

easy/controller/meta.py

Lines changed: 69 additions & 129 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 MODEL_FIELDS_ATTR_DEFAULT, ModelOptions
1515
from easy.domain.orm import CrudModel
1616
from easy.response import BaseApiResponse
1717
from easy.services import BaseService
@@ -20,91 +20,25 @@
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)
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

9340
class 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

Comments
 (0)