Skip to content

Commit 252283e

Browse files
committed
Refactored ModelController, moved crud operation to modelservice and moved model controller settings to model_config
1 parent 0bb12a9 commit 252283e

File tree

11 files changed

+662
-475
lines changed

11 files changed

+662
-475
lines changed

.coveragerc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ exclude_lines =
55
def __str__
66
if self.debug:
77
if settings.DEBUG
8+
if t.TYPE_CHECKING:
89
raise AssertionError
910
raise NotImplementedError
1011
if 0:
1112
if __name__ == .__main__.:
1213
class .*\bProtocol\):
13-
@(abc\.)?abstractmethod
14+
@(abc\.)?abstractmethod

ninja_extra/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66

77
from ninja_extra.controllers import (
88
ControllerBase,
9+
ModelConfig,
910
ModelControllerBase,
11+
ModelControllerBuilder,
12+
ModelPagination,
13+
ModelSchemeConfig,
14+
ModelService,
15+
ModelServiceBase,
1016
api_controller,
1117
http_delete,
1218
http_generic,
@@ -27,7 +33,6 @@
2733

2834

2935
__all__ = [
30-
"ModelControllerBase",
3136
"ControllerBase",
3237
"api_controller",
3338
"NinjaExtraAPI",
@@ -48,4 +53,12 @@
4853
"Router",
4954
"throttle",
5055
"paginate",
56+
"ModelControllerBase",
57+
"ModelConfig",
58+
"ModelService",
59+
"ModelSchemeConfig",
60+
"ModelControllerBuilder",
61+
"ModelPagination",
62+
"ModelServiceBase",
63+
"ModelControllerBase",
5164
]

ninja_extra/controllers/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
from .base import ControllerBase, ModelControllerBase, api_controller
2+
from .model import (
3+
ModelConfig,
4+
ModelControllerBuilder,
5+
ModelPagination,
6+
ModelSchemeConfig,
7+
ModelService,
8+
ModelServiceBase,
9+
)
210
from .response import Detail, Id, Ok
311
from .route import (
412
Route,
@@ -33,4 +41,10 @@
3341
"Detail",
3442
"RouteContext",
3543
"ModelControllerBase",
44+
"ModelConfig",
45+
"ModelService",
46+
"ModelSchemeConfig",
47+
"ModelControllerBuilder",
48+
"ModelPagination",
49+
"ModelServiceBase",
3650
]

ninja_extra/controllers/base.py

Lines changed: 4 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import inspect
22
import re
3-
import traceback
43
import uuid
5-
from abc import ABC, abstractmethod
4+
from abc import ABC
65
from typing import (
76
TYPE_CHECKING,
87
Any,
@@ -27,12 +26,9 @@
2726
from injector import inject, is_decorated_with_inject
2827
from ninja import NinjaAPI, Router
2928
from ninja.constants import NOT_SET
30-
from ninja.pagination import PaginationBase
3129
from ninja.security.base import AuthBase
3230
from ninja.signature import is_async
3331
from ninja.utils import normalize_path
34-
from pydantic import BaseModel as PydanticModel
35-
from pydantic import Field, validator
3632

3733
from ninja_extra.constants import ROUTE_FUNCTION, THROTTLED_FUNCTION
3834
from ninja_extra.exceptions import APIException, NotFound, PermissionDenied, bad_request
@@ -47,8 +43,7 @@
4743
)
4844
from ninja_extra.types import PermissionType
4945

50-
from ..pagination import PageNumberPaginationExtra, PaginatedResponseSchema
51-
from .model_controller_builder import ModelControllerBuilder
46+
from .model import ModelConfig, ModelControllerBuilder, ModelService
5247
from .registry import ControllerRegistry
5348
from .response import Detail, Id, Ok
5449
from .route.route_functions import AsyncRouteFunction, RouteFunction
@@ -226,154 +221,8 @@ def create_response(
226221
)
227222

228223

229-
class ModelServiceBase(ABC):
230-
@abstractmethod
231-
def get_one(self, pk: Any) -> Any:
232-
pass
233-
234-
@abstractmethod
235-
def get_all(self) -> QuerySet:
236-
pass
237-
238-
@abstractmethod
239-
def create(self, schema: PydanticModel, **kwargs: Any) -> Any:
240-
pass
241-
242-
@abstractmethod
243-
def update(self, instance: Model, schema: PydanticModel, **kwargs: Any) -> Any:
244-
pass
245-
246-
@abstractmethod
247-
def patch(self, instance: Model, schema: PydanticModel, **kwargs: Any) -> Any:
248-
pass
249-
250-
@abstractmethod
251-
def delete(self, instance: Model) -> Any:
252-
pass
253-
254-
255-
class ModelService(ModelServiceBase):
256-
def __init__(self, model: Type[Model]) -> None:
257-
self.model = model
258-
259-
def get_one(self, pk: Any) -> Any:
260-
obj = get_object_or_exception(
261-
klass=self.model, error_message=None, exception=NotFound, pk=pk
262-
)
263-
return obj
264-
265-
def get_all(self) -> QuerySet:
266-
return self.model.objects.all()
267-
268-
def create(self, schema: PydanticModel, **kwargs: Any) -> Any:
269-
data = schema.dict(by_alias=True)
270-
data.update(kwargs)
271-
272-
try:
273-
instance = self.model._default_manager.create(**data)
274-
return instance
275-
except TypeError as tex:
276-
tb = traceback.format_exc()
277-
msg = (
278-
"Got a `TypeError` when calling `%s.%s.create()`. "
279-
"This may be because you have a writable field on the "
280-
"serializer class that is not a valid argument to "
281-
"`%s.%s.create()`. You may need to make the field "
282-
"read-only, or override the %s.create() method to handle "
283-
"this correctly.\nOriginal exception was:\n %s"
284-
% (
285-
self.model.__name__,
286-
self.model._default_manager.name,
287-
self.model.__name__,
288-
self.model._default_manager.name,
289-
self.__class__.__name__,
290-
tb,
291-
)
292-
)
293-
raise TypeError(msg) from tex
294-
295-
def update(self, instance: Model, schema: PydanticModel, **kwargs: Any) -> Any:
296-
data = schema.dict(exclude_none=True)
297-
data.update(kwargs)
298-
for attr, value in data.items():
299-
setattr(instance, attr, value)
300-
instance.save()
301-
return instance
302-
303-
def patch(self, instance: Model, schema: PydanticModel, **kwargs: Any) -> Any:
304-
return self.update(instance=instance, schema=schema, **kwargs)
305-
306-
def delete(self, instance: Model) -> Any:
307-
instance.delete()
308-
309-
310-
class ModelConfigSchema(Tuple):
311-
in_schema: Type[PydanticModel]
312-
out_schema: Optional[Type[PydanticModel]]
313-
314-
def get_out_schema(self) -> Type[PydanticModel]:
315-
if not self.out_schema:
316-
return self.in_schema
317-
return self.out_schema
318-
319-
320-
class ModelPagination(PydanticModel):
321-
klass: Type[PaginationBase] = PageNumberPaginationExtra
322-
paginate_by: Optional[int] = None
323-
schema: Type[PydanticModel] = PaginatedResponseSchema
324-
325-
@validator("klass")
326-
def validate_klass(cls, value: Any) -> Any:
327-
if not issubclass(PaginationBase, value):
328-
raise ValueError(f"{value} is not of type `PaginationBase`")
329-
return value
330-
331-
@validator(
332-
"schema",
333-
)
334-
def validate_schema(cls, value: Any) -> Any:
335-
if not issubclass(PydanticModel, value):
336-
raise ValueError(
337-
f"{value} is not a valid type. Please use a generic pydantic model."
338-
)
339-
return value
340-
341-
342-
class ModelConfig(PydanticModel):
343-
allowed_routes: List[str] = Field(
344-
[
345-
"create",
346-
"read",
347-
"update",
348-
"patch",
349-
"delete",
350-
"list",
351-
]
352-
)
353-
create_schema: ModelConfigSchema
354-
update_schema: ModelConfigSchema
355-
patch_schema: Optional[ModelConfigSchema] = None
356-
retrieve_schema: Type[PydanticModel]
357-
pagination: ModelPagination = Field(default=ModelPagination())
358-
model: Type[Model]
359-
360-
@validator("allowed_routes")
361-
def validate_allow_routes(cls, value: List[Any]) -> Any:
362-
defaults = ["create", "read", "update", "patch", "delete", "list"]
363-
for item in value:
364-
if item not in defaults:
365-
raise ValueError(f"{item} action is not recognized in {defaults}")
366-
return value
367-
368-
@validator("model")
369-
def validate_model(cls, value: Any) -> Any:
370-
if value and hasattr(value, "objects"):
371-
return value
372-
raise ValueError(f"{value} is not a valid Django model.")
373-
374-
375224
class ModelControllerBase(ControllerBase):
376-
service: Optional[ModelService] = None
225+
service: ModelService
377226
model_config: Optional[ModelConfig] = None
378227

379228

@@ -498,7 +347,7 @@ def __call__(self, cls: Type) -> Union[Type, Type["ControllerBase"]]:
498347
if issubclass(cls, ModelControllerBase):
499348
if cls.model_config:
500349
# if model_config is not provided, treat controller class as normal
501-
builder = ModelControllerBuilder(cls.model_config, self)
350+
builder = ModelControllerBuilder(cls, self)
502351
builder.register_model_routes()
503352
# We create a global service for handle CRUD Operations at class level
504353
# giving room for it to be changed at instance level through Dependency injection
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from .builder import ModelControllerBuilder
2+
from .interfaces import ModelServiceBase
3+
from .schemas import ModelConfig, ModelPagination, ModelSchemeConfig
4+
from .service import ModelService
5+
6+
__all__ = [
7+
"ModelServiceBase",
8+
"ModelService",
9+
"ModelConfig",
10+
"ModelSchemeConfig",
11+
"ModelPagination",
12+
"ModelControllerBuilder",
13+
]

0 commit comments

Comments
 (0)