Skip to content

Commit 679762f

Browse files
authored
Merge pull request #8 from eadwinCode/default_app_config_fix
Default App config Fix for Django >= 4.0 Added support for django-ninja==0.17.0
2 parents 5898110 + fd0b27e commit 679762f

File tree

6 files changed

+86
-40
lines changed

6 files changed

+86
-40
lines changed

ninja_extra/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
__version__ = "0.14.2"
44

5+
import django
6+
57
from ninja_extra.controllers import (
68
ControllerBase,
79
api_controller,
@@ -17,7 +19,9 @@
1719
from ninja_extra.main import NinjaExtraAPI
1820
from ninja_extra.router import Router
1921

20-
default_app_config = "ninja_extra.apps.NinjaExtraConfig"
22+
if django.VERSION < (3, 2): # pragma: no cover
23+
default_app_config = "ninja_extra.apps.NinjaExtraConfig"
24+
2125

2226
__all__ = [
2327
"ControllerBase",

ninja_extra/pagination.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ninja import Schema
1212
from ninja.constants import NOT_SET
1313
from ninja.pagination import LimitOffsetPagination, PageNumberPagination, PaginationBase
14-
from ninja.signature import has_kwargs, is_async
14+
from ninja.signature import is_async
1515
from ninja.types import DictStrAny
1616
from pydantic import Field
1717

@@ -66,8 +66,9 @@ def __init__(
6666
self,
6767
page_size: int = settings.PAGINATION_PER_PAGE,
6868
max_page_size: Optional[int] = None,
69+
pass_parameter: Optional[str] = None,
6970
) -> None:
70-
super().__init__()
71+
super().__init__(pass_parameter=pass_parameter)
7172
self.page_size = page_size
7273
self.max_page_size = max_page_size or 200
7374
self.Input = self.create_input() # type:ignore
@@ -79,14 +80,17 @@ class DynamicInput(PageNumberPaginationExtra.Input):
7980

8081
return DynamicInput
8182

82-
def paginate_queryset(
83-
self, items: QuerySet, request: HttpRequest, **params: Any
83+
def paginate_queryset( # type: ignore
84+
self,
85+
queryset: QuerySet,
86+
pagination: Input,
87+
request: Optional[HttpRequest] = None,
88+
**params: DictStrAny,
8489
) -> Any:
85-
86-
pagination_input = cast(PageNumberPaginationExtra.Input, params["pagination"])
87-
page_size = self.get_page_size(pagination_input.page_size)
88-
current_page_number = pagination_input.page
89-
paginator = self.paginator_class(items, page_size)
90+
assert request, "request is required"
91+
page_size = self.get_page_size(pagination.page_size)
92+
current_page_number = pagination.page
93+
paginator = self.paginator_class(queryset, page_size)
9094
try:
9195
url = request.build_absolute_uri()
9296
page: Page = paginator.page(current_page_number)
@@ -197,13 +201,7 @@ def __init__(
197201
self.paginator = paginator
198202
self.paginator_kwargs_name = paginator_kwargs_name
199203
self.view_func = view_func
200-
self.view_func_has_kwargs = True
201204

202-
if not has_kwargs(view_func):
203-
self.view_func_has_kwargs = False
204-
logger.debug(
205-
f"function {view_func.__name__} should have **kwargs if you want to use pagination parameters"
206-
)
207205
paginator_view = self.get_view_function()
208206
paginator_view._ninja_contribute_args = [ # type: ignore
209207
(
@@ -215,11 +213,16 @@ def __init__(
215213
setattr(paginator_view, "paginator_operation", self)
216214
self.as_view = wraps(view_func)(paginator_view)
217215

216+
@property
217+
def view_func_has_kwargs(self) -> bool:
218+
return self.paginator.pass_parameter is not None
219+
218220
def get_view_function(self) -> Callable:
219221
def as_view(controller: "ControllerBase", *args: Any, **kw: Any) -> Any:
220-
func_kwargs = dict(kw)
221-
if not self.view_func_has_kwargs:
222-
func_kwargs.pop(self.paginator_kwargs_name)
222+
func_kwargs = dict(**kw)
223+
pagination_params = func_kwargs.pop(self.paginator_kwargs_name)
224+
if self.paginator.pass_parameter:
225+
func_kwargs[self.paginator.pass_parameter] = pagination_params
223226

224227
items = self.view_func(controller, *args, **func_kwargs)
225228
assert (
@@ -235,9 +238,10 @@ def as_view(controller: "ControllerBase", *args: Any, **kw: Any) -> Any:
235238
class AsyncPaginatorOperation(PaginatorOperation):
236239
def get_view_function(self) -> Callable:
237240
async def as_view(controller: "ControllerBase", *args: Any, **kw: Any) -> Any:
238-
func_kwargs = dict(kw)
239-
if not self.view_func_has_kwargs:
240-
func_kwargs.pop(self.paginator_kwargs_name)
241+
func_kwargs = dict(**kw)
242+
pagination_params = func_kwargs.pop(self.paginator_kwargs_name)
243+
if self.paginator.pass_parameter:
244+
func_kwargs[self.paginator.pass_parameter] = pagination_params
241245

242246
items = await self.view_func(controller, *args, **func_kwargs)
243247
assert (

ninja_extra/schemas/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
from .response import PaginatedResponseSchema, RouteParameter
1+
from .response import (
2+
NinjaPaginationResponseSchema,
3+
PaginatedResponseSchema,
4+
RouteParameter,
5+
)
26

3-
__all__ = ["PaginatedResponseSchema", "RouteParameter"]
7+
__all__ = ["PaginatedResponseSchema", "RouteParameter", "NinjaPaginationResponseSchema"]

ninja_extra/schemas/response.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ class BasePaginatedResponseSchema(Schema):
1919
results: List[Any]
2020

2121

22+
class BaseNinjaResponseSchema(Schema):
23+
count: int
24+
items: List[Any]
25+
26+
2227
class PaginatedResponseSchema(GenericType):
2328
def get_generic_type(
2429
self, wrap_type: Any
@@ -32,13 +37,31 @@ class ListResponseSchema(BasePaginatedResponseSchema):
3237
return ListResponseSchema
3338

3439

40+
class NinjaPaginationResponseSchema(GenericType):
41+
def get_generic_type(
42+
self, wrap_type: Any
43+
) -> Type[BaseNinjaResponseSchema]: # pragma: no cover
44+
class ListNinjaResponseSchema(BaseNinjaResponseSchema):
45+
items: List[wrap_type] # type: ignore
46+
47+
ListNinjaResponseSchema.__name__ = (
48+
f"{self.__class__.__name__}[{str(wrap_type.__name__).capitalize()}]"
49+
)
50+
return ListNinjaResponseSchema
51+
52+
3553
if sys.version_info >= (3, 8): # pragma: no cover
3654

3755
class PaginatedResponseSchema(
3856
GenericModel, Generic[T], BasePaginatedResponseSchema
3957
):
4058
results: List[T]
4159

60+
class NinjaPaginationResponseSchema(
61+
GenericModel, Generic[T], BaseNinjaResponseSchema
62+
):
63+
items: List[T]
64+
4265

4366
class RouteParameter(BaseModel):
4467
path: str

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ test = [
6363
"isort",
6464
"injector",
6565
"flake8",
66-
"mypy",
66+
"mypy==0.931",
6767
"django-stubs",
6868
]
6969
doc = []

tests/test_pagination.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ def paginate_queryset(self, items, request, **params):
3333
class SomeAPIController:
3434
@route.get("/items_1")
3535
@paginate # WITHOUT brackets (should use default pagination)
36-
def items_1(self, **kwargs):
36+
def items_1(self):
3737
return ITEMS
3838

3939
@route.get("/items_2")
4040
@paginate() # with brackets (should use default pagination)
41-
def items_2(self, someparam: int = 0, **kwargs):
41+
def items_2(self, someparam: int = 0):
4242
# also having custom param `someparam` - that should not be lost
4343
return ITEMS
4444

4545
@route.get("/items_3")
46-
@paginate(CustomPagination)
46+
@paginate(CustomPagination, pass_parameter="pass_kwargs")
4747
def items_3(self, **kwargs):
4848
return ITEMS
4949

5050
@route.get("/items_4")
51-
@paginate(PageNumberPaginationExtra, page_size=10)
51+
@paginate(PageNumberPaginationExtra, page_size=10, pass_parameter="pass_kwargs")
5252
def items_4(self, **kwargs):
5353
return ITEMS
5454

@@ -72,7 +72,7 @@ def test_paginator_operation_used(self):
7272
SomeAPIController, lambda member: isinstance(member, RouteFunction)
7373
)
7474
}
75-
has_kwargs = ("items_1", "items_3", "items_4")
75+
has_kwargs = ("items_3", "items_4")
7676
for name, route_function in some_api_route_functions.items():
7777
assert hasattr(route_function.as_view, "paginator_operation")
7878
paginator_operation = route_function.as_view.paginator_operation
@@ -82,7 +82,8 @@ def test_paginator_operation_used(self):
8282

8383
def test_case1(self):
8484
response = client.get("/items_1?limit=10").json()
85-
assert response == ITEMS[:10]
85+
assert response.get("items")
86+
assert response["items"] == ITEMS[:10]
8687

8788
schema = api.get_openapi_schema()["paths"]["/api/items_1"]["get"]
8889
# print(schema)
@@ -113,7 +114,8 @@ def test_case1(self):
113114

114115
def test_case2(self):
115116
response = client.get("/items_2?limit=10").json()
116-
assert response == ITEMS[:10]
117+
assert response.get("items")
118+
assert response["items"] == ITEMS[:10]
117119

118120
schema = api.get_openapi_schema()["paths"]["/api/items_2"]["get"]
119121
# print(schema["parameters"])
@@ -199,7 +201,8 @@ def test_case4(self):
199201

200202
def test_case5(self):
201203
response = client.get("/items_5?page=2").json()
202-
assert response == ITEMS[10:20]
204+
assert response.get("items")
205+
assert response["items"] == ITEMS[10:20]
203206

204207
schema = api.get_openapi_schema()["paths"]["/api/items_5"]["get"]
205208
# print(schema)
@@ -232,17 +235,19 @@ async def items_1(self, **kwargs):
232235

233236
@route.get("/items_2")
234237
@paginate() # with brackets (should use default pagination)
235-
async def items_2(self, someparam: int = 0, **kwargs):
238+
async def items_2(self, someparam: int = 0):
236239
# also having custom param `someparam` - that should not be lost
237240
return ITEMS
238241

239242
@route.get("/items_3")
240-
@paginate(CustomPagination)
243+
@paginate(CustomPagination, pass_parameter="pass_kwargs")
241244
async def items_3(self, **kwargs):
242245
return ITEMS
243246

244247
@route.get("/items_4")
245-
@paginate(PageNumberPaginationExtra, page_size=10)
248+
@paginate(
249+
PageNumberPaginationExtra, page_size=10, pass_parameter="pass_kwargs"
250+
)
246251
async def items_4(self, **kwargs):
247252
return ITEMS
248253

@@ -263,7 +268,7 @@ async def test_paginator_operation_used(self):
263268
lambda member: isinstance(member, RouteFunction),
264269
)
265270
}
266-
has_kwargs = ("items_1", "items_3", "items_4")
271+
has_kwargs = ("items_3", "items_4")
267272
for name, route_function in some_api_route_functions.items():
268273
assert hasattr(route_function.as_view, "paginator_operation")
269274
paginator_operation = route_function.as_view.paginator_operation
@@ -273,7 +278,9 @@ async def test_paginator_operation_used(self):
273278

274279
async def test_case1(self):
275280
response = await self.client.get("/items_1?limit=10")
276-
assert response.json() == ITEMS[:10]
281+
data = response.json()
282+
assert data.get("items")
283+
assert data["items"] == ITEMS[:10]
277284

278285
schema = self.api_async.get_openapi_schema()["paths"]["/api/items_1"]["get"]
279286
# print(schema)
@@ -304,7 +311,9 @@ async def test_case1(self):
304311

305312
async def test_case2(self):
306313
response = await self.client.get("/items_2?limit=10")
307-
assert response.json() == ITEMS[:10]
314+
data = response.json()
315+
assert data.get("items")
316+
assert data["items"] == ITEMS[:10]
308317

309318
async def test_case3(self):
310319
response = await self.client.get("/items_3?skip=5")
@@ -320,4 +329,6 @@ async def test_case4(self):
320329

321330
async def test_case5(self):
322331
response = await self.client.get("/items_5?page=2")
323-
assert response.json() == ITEMS[10:20]
332+
data = response.json()
333+
assert data.get("items")
334+
assert data["items"] == ITEMS[10:20]

0 commit comments

Comments
 (0)