Skip to content

Commit e4a6e85

Browse files
authored
Merge pull request #53 from QtiQla/master
add searching and ordering
2 parents 398d6d0 + 65c5c2c commit e4a6e85

File tree

13 files changed

+1317
-15
lines changed

13 files changed

+1317
-15
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ repos:
1414
name: Code Formatting
1515
entry: "make fmt"
1616
types: [python]
17-
language_version: python3.8
17+
language_version: python3
1818
language: python
1919
- id: code_linting
2020
args: [ ]
2121
name: Code Linting
2222
entry: "make lint"
2323
types: [ python ]
24-
language_version: python3.8
24+
language_version: python3
2525
language: python
2626
- repo: https://github.com/pre-commit/pre-commit-hooks
2727
rev: v2.3.0

docs/settings.md

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# **Settings**
2+
23
Django-Ninja-Extra has some settings that can be overridden by adding a `NINJA_EXTRA` field in Django `settings.py` with some key-value pair as shown below:
34

45
```python
@@ -17,40 +18,54 @@ NINJA_EXTRA = {
1718
'user': '1000/day',
1819
'anon': '100/day',
1920
},
20-
'NUM_PROXIES': None
21+
'NUM_PROXIES': None,
22+
'ORDERING_CLASS':"ninja_extra.ordering.Ordering",
23+
'SEARCHING_CLASS':"ninja_extra.searching.Search",
2124
}
2225
```
26+
2327
You can override what you don't need. It is not necessary need to override everything.
2428

25-
`PAGINATION_CLASS`
26-
=======================
27-
It defines the default paginator class used by the `paginate` decorator
29+
# `PAGINATION_CLASS`
30+
31+
It defines the default paginator class used by the `paginate` decorator
2832
function if a paginator class is not defined.
2933
default: `ninja_extra.pagination.LimitOffsetPagination`
3034

31-
`PAGINATION_PER_PAGE`
32-
=======================
35+
# `PAGINATION_PER_PAGE`
36+
3337
It defines the default page size that is passed to the `PAGINATION_CLASS` during instantiation.
3438
default: `100`
3539

40+
# `INJECTOR_MODULES`
3641

37-
`INJECTOR_MODULES`
38-
=======================
3942
It contains a list of strings that defines the path to injector `Module`.
4043
default: `[]`
4144

42-
`THROTTLE_CLASSES`
43-
=======================
45+
# `THROTTLE_CLASSES`
46+
4447
It contains a list of strings that defines the path default throttling classes.
4548
default: `[
4649
"ninja_extra.throttling.AnonRateThrottle",
4750
"ninja_extra.throttling.UserRateThrottle",
4851
]`
4952

50-
`THROTTLE_RATES`
51-
=======================
53+
# `THROTTLE_RATES`
54+
5255
It contains a key-value pair of different throttling rates which are applies to different `THROTTLING_CLASSES`.
5356
default: `{
5457
'user': '1000/day',
5558
'anon': '100/day',
5659
}`
60+
61+
# `ORDERING_CLASS`
62+
63+
It defines the default ordering class used by the `ordering` decorator
64+
function if a ordering class is not defined.
65+
default: `ninja_extra.ordering.Ordering`
66+
67+
# `SEARCHING_CLASS`
68+
69+
It defines the default searching class used by the `searching` decorator
70+
function if a searching class is not defined.
71+
default: `ninja_extra.searching.Searching`

docs/tutorial/ordering.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# **Ordering**
2+
3+
**Django Ninja Extra** provides an intuitive ordering model using `ordering` decoration from the Django-Ninja-Extra ordering module. It expects a Queryset from as a route function result.
4+
5+
> This feature was inspired by the [DRF OrderingFilter](https://www.django-rest-framework.org/api-guide/filtering/#orderingfilter)
6+
7+
## **Properties**
8+
9+
`def ordering(func_or_ordering_class: Any = NOT_SET, **ordering_params: Any) -> Callable[..., Any]:`
10+
11+
- func_or_ordering_class: Defines a route function or an Ordering Class. default: `ninja_extra.ordering.Ordering`
12+
- ordering_params: extra parameters for initialising Ordering Class
13+
14+
### Changing Default Ordering Class
15+
16+
To change the default ordering class, you need to add a `NINJA_EXTRA` variable in `settings.py` with a key `ORDERING_CLASS` and value defining path to ordering class
17+
18+
```python
19+
# Django project settings.py
20+
INSTALLED_APPS = [
21+
...
22+
]
23+
NINJA_EXTRA={
24+
'ORDERING_CLASS': 'someapp.somemodule.CustomOrdering'
25+
}
26+
```
27+
28+
## **Usage**
29+
30+
- If you do not specify the `ordering_fields` parameter, all fields from the QuerySet will be used for ordering.
31+
- For example, to order users by username:
32+
> http://example.com/api/users?ordering=username
33+
- The client may also specify reverse orderings by prefixing the field name with '-', example:
34+
> http://example.com/api/users?ordering=-username
35+
- Multiple orderings may also be specified:
36+
> http://example.com/api/users?ordering=username,email
37+
38+
```python
39+
from typing import List
40+
from ninja_extra.ordering import ordering, Ordering
41+
from ninja_extra import api_controller, route, NinjaExtraAPI
42+
from ninja import ModelSchema
43+
from django.contrib.auth import get_user_model
44+
45+
user_model = get_user_model()
46+
47+
48+
class UserSchema(ModelSchema):
49+
class Config:
50+
model = user_model
51+
model_fields = ['username', 'email']
52+
53+
54+
@api_controller('/users')
55+
class UserController:
56+
@route.get('', response=List[UserSchema])
57+
@ordering(Ordering, ordering_fields=['username', 'email'])
58+
def get_users(self):
59+
return user_model.objects.all()
60+
61+
@route.get('/all-sort', response=List[UserSchema])
62+
@ordering
63+
def get_users_with_all_field_ordering(self):
64+
return user_model.objects.all()
65+
66+
67+
api = NinjaExtraAPI(title='Ordering Test')
68+
api.register_controllers(UserController)
69+
```
70+
71+
## Note
72+
73+
> If you use the `paginate` decorator and the `ordering` decorator together, the `paginate` decorator should be above the `ordering` decorator because first the data are sorted and then the data are paginated, for example:
74+
>
75+
> ```python
76+
> @route.get('', response=List[UserSchema])
77+
> @paginate
78+
> @ordering(Ordering, ordering_fields=['username', 'email'])
79+
> def get_users(self):
80+
> return user_model.objects.all()
81+
> ```

docs/tutorial/searching.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# **Searching**
2+
3+
**Django Ninja Extra** provides an intuitive searching model using `searching` decoration from the Django-Ninja-Extra searching module. It expects a Queryset from as a route function result.
4+
5+
> This feature was inspired by the [DRF SearchFilter](https://www.django-rest-framework.org/api-guide/filtering/#searchfilter)
6+
7+
## **Properties**
8+
9+
`def searching(func_or_searching_class: Any = NOT_SET, **searching_params: Any) -> Callable[..., Any]:`
10+
11+
- func_or_searching_class: Defines a route function or an Searching Class. default: `ninja_extra.searching.Searching`
12+
- searching_params: extra parameters for initialising Searching Class
13+
14+
### Changing Default Searching Class
15+
16+
To change the default searching class, you need to add a `NINJA_EXTRA` variable in `settings.py` with a key `SEARCHING_CLASS` and value defining path to searching class
17+
18+
```python
19+
# Django project settings.py
20+
INSTALLED_APPS = [
21+
...
22+
]
23+
NINJA_EXTRA={
24+
'SEARCHING_CLASS': 'someapp.somemodule.CustomSearching'
25+
}
26+
```
27+
28+
## **Usage**
29+
30+
- If you do not specify the `search_fields` parameter, will return the result without change.
31+
- For example, to search users by username or email:
32+
> http://example.com/api/users?search=someuser
33+
- You can also perform a related lookup on a ForeignKey or ManyToManyField with the lookup API double-underscore notation:
34+
> search_fields = ['username', 'email', 'profile__profession']
35+
- By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched. The search behavior may be restricted by prepending various characters to the `search_fields`.
36+
37+
* '^' Starts-with search.
38+
* '=' Exact matches.
39+
* '@' Full-text search. (Currently only supported Django's [PostgreSQL backend](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/search/).)
40+
* '$' Regex search.
41+
42+
For example:
43+
44+
> search_fields = ['=username', '=email']
45+
46+
```python
47+
from typing import List
48+
from ninja_extra.searching import searching, Searching
49+
from ninja_extra import api_controller, route, NinjaExtraAPI
50+
from ninja import ModelSchema
51+
from django.contrib.auth import get_user_model
52+
53+
user_model = get_user_model()
54+
55+
56+
class UserSchema(ModelSchema):
57+
class Config:
58+
model = user_model
59+
model_fields = ['username', 'email']
60+
61+
62+
@api_controller('/users')
63+
class UserController:
64+
@route.get('', response=List[UserSchema])
65+
@searching(Searching, search_fields=['username', 'email'])
66+
def get_users(self):
67+
return user_model.objects.all()
68+
69+
@route.get('/iexact-email', response=List[UserSchema])
70+
@searching(search_fields=['=email'])
71+
def get_users_with_search_iexact_email(self):
72+
return user_model.objects.all()
73+
74+
75+
api = NinjaExtraAPI(title='Searching Test')
76+
api.register_controllers(UserController)
77+
```
78+
79+
## Note
80+
81+
> If you use the `paginate` decorator, the `ordering` decorator and the `searching` decorator together, the `paginate` decorator should be above the `ordering` decorator and the `ordering` decorator should be above the `searching` decorator because first the data is filtered, then the data is sorted and then paginated:, for example:
82+
>
83+
> ```python
84+
> @route.get('', response=List[UserSchema])
85+
> @paginate
86+
> @ordering(Ordering, ordering_fields=['username', 'email'])
87+
> @searching(Searching, search_fields=['username', 'email'])
88+
> def get_users(self):
89+
> return user_model.objects.all()
90+
> ```

mkdocs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ nav:
4646
- Form Request: tutorial/form.md
4747
- Schema: tutorial/schema.md
4848
- Pagination: tutorial/pagination.md
49+
- Filtering:
50+
- Ordering: tutorial/ordering.md
51+
- Searching: tutorial/searching.md
4952
- Error Handling: tutorial/custom_exception.md
5053
- Versioning: tutorial/versioning.md
5154
- Throttling: tutorial/throttling.md

ninja_extra/conf/settings.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def __init__(self, data: dict) -> None:
2121
"ninja_extra.throttling.UserRateThrottle",
2222
],
2323
THROTTLE_RATES={"user": None, "anon": None},
24+
ORDERING_CLASS="ninja_extra.ordering.Ordering",
25+
SEARCHING_CLASS="ninja_extra.searching.Searching",
2426
)
2527

2628
USER_SETTINGS = UserDefinedSettingsMapper(
@@ -43,6 +45,12 @@ class Config:
4345
THROTTLE_CLASSES: List[Any] = []
4446
NUM_PROXIES: Optional[int] = None
4547
INJECTOR_MODULES: List[Any] = []
48+
ORDERING_CLASS: Any = Field(
49+
"ninja_extra.ordering.Ordering",
50+
)
51+
SEARCHING_CLASS: Any = Field(
52+
"ninja_extra.searching.Searching",
53+
)
4654

4755
@validator("INJECTOR_MODULES", pre=True)
4856
def pre_injector_module_validate(cls, value: Any) -> Any:
@@ -62,6 +70,18 @@ def pre_pagination_class_validate(cls, value: Any) -> Any:
6270
raise ValueError("Invalid data type")
6371
return value
6472

73+
@validator("ORDERING_CLASS", pre=True)
74+
def pre_ordering_class_validate(cls, value: Any) -> Any:
75+
if isinstance(value, list):
76+
raise ValueError("Invalid data type")
77+
return value
78+
79+
@validator("SEARCHING_CLASS", pre=True)
80+
def pre_searching_class_validate(cls, value: Any) -> Any:
81+
if isinstance(value, list):
82+
raise ValueError("Invalid data type")
83+
return value
84+
6585
@root_validator
6686
def validate_ninja_extra_settings(cls, values: Any) -> Any:
6787
for item in NinjaExtra_SETTINGS_DEFAULTS.keys():

0 commit comments

Comments
 (0)