Skip to content

Commit 01b39d4

Browse files
committed
Merge branch 'master' into feat/add-uniontype-check
And fix type(arg) instead of type(None)
2 parents d87cdda + 1b46ff1 commit 01b39d4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1994
-346
lines changed

.github/FUNDING.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# Please support Ukraine volonteers
1+
# polar: django-ninja
22
custom: ["https://www.buymeacoffee.com/djangoninja"]

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
- name: Set up Python
1515
uses: actions/setup-python@v5
1616
with:
17-
python-version: 3.9
17+
python-version: '3.10'
1818
- name: Install Flit
19-
run: pip install flit
19+
run: pip install flit "django>=5.1"
2020
- name: Install Dependencies
2121
run: flit install --symlink
2222
- name: Test

.github/workflows/test_full.yml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ on:
88

99
jobs:
1010
test:
11-
runs-on: ubuntu-latest
11+
runs-on: ubuntu-22.04
1212
strategy:
1313
matrix:
14-
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
15-
django-version: ['<3.2', '<3.3', '<4.2', '<4.3', '<5.1']
14+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
15+
django-version: ['<3.2', '<3.3', '<4.2', '<4.3', '<5.1', '<5.2', '<5.3']
1616
exclude:
1717
- python-version: '3.7'
1818
django-version: '<5.1'
@@ -24,7 +24,10 @@ jobs:
2424
django-version: '<3.2'
2525
- python-version: '3.12'
2626
django-version: '<3.3'
27-
27+
- python-version: '3.13'
28+
django-version: '<3.2'
29+
- python-version: '3.13'
30+
django-version: '<3.3'
2831
steps:
2932
- uses: actions/checkout@v3
3033
- name: Set up Python
@@ -46,9 +49,9 @@ jobs:
4649
- name: Set up Python
4750
uses: actions/setup-python@v5
4851
with:
49-
python-version: 3.9
52+
python-version: '3.10'
5053
- name: Install Flit
51-
run: pip install flit
54+
run: pip install flit "django>=5.2"
5255
- name: Install Dependencies
5356
run: flit install --symlink
5457
- name: Test
@@ -67,8 +70,8 @@ jobs:
6770
- name: Install Dependencies
6871
run: flit install --symlink
6972
- name: Ruff format
70-
run: ruff format --check ninja tests
73+
run: ruff format --preview --check ninja tests
7174
- name: Ruff lint
72-
run: ruff check ninja tests
75+
run: ruff check --preview ninja tests
7376
- name: mypy
7477
run: mypy ninja tests/mypy_test.py

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ docs/site
1313

1414
.DS_Store
1515
.idea
16+
.python-version

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2023 Vitaliy Kucheryaviy
3+
Copyright (c) 2025 Vitaliy Kucheryaviy
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,6 @@
2222

2323
# Django Ninja - Fast Django REST Framework
2424

25-
# v1.0 What's new
26-
Read more details here - https://django-ninja.dev/whatsnew_v1/
27-
28-
Or Watch here:
29-
30-
31-
<a href="https://youtu.be/GrIpDXPG41o"><img width="500" alt="SCR-20231116-qmoj" src="https://github.com/vitalik/django-ninja/assets/95222/06958fbf-6d3a-4f33-aa76-7a29279c9959"></a>
32-
33-
34-
3525
**Django Ninja** is a web framework for building APIs with **Django** and Python 3.6+ **type hints**.
3626

3727

docs/docs/guides/api-docs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ In a Django template, for example:
101101

102102
## Creating custom docs viewer
103103

104-
To create your own view for OpenaAPI - create a class inherited from DocsBase and overwrite `render_page` method:
104+
To create your own view for OpenAPI - create a class inherited from DocsBase and overwrite `render_page` method:
105105

106106
```python
107107
form ninja.openapi.docs import DocsBase

docs/docs/guides/async-support.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ async def search(request, post_id: int):
187187
...
188188
```
189189

190-
There is a common **GOTCHA**: Django queryset's are lazily evaluated (database query happens only when you start iterating), so this will **not** work:
190+
There is a common **GOTCHA**: Django querysets are lazily evaluated (database query happens only when you start iterating), so this will **not** work:
191191

192192
```python
193193
all_blogs = await sync_to_async(Blog.objects.all)()

docs/docs/guides/authentication.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,48 @@ Note: **`param_name`** is the name of the GET parameter that will be checked for
126126
{!./src/tutorial/authentication/apikey03.py!}
127127
```
128128

129+
### Django Session Authentication
130+
131+
**Django Ninja** provides built-in session authentication classes that leverage Django's existing session framework:
132+
133+
#### SessionAuth
134+
135+
Uses Django's default session authentication - authenticates any logged-in user:
136+
137+
```python
138+
from ninja.security import SessionAuth
139+
140+
@api.get("/protected", auth=SessionAuth())
141+
def protected_view(request):
142+
return {"user": request.auth.username}
143+
```
144+
145+
#### SessionAuthSuperUser
146+
147+
Authenticates only users with superuser privileges:
148+
149+
```python
150+
from ninja.security import SessionAuthSuperUser
151+
152+
@api.get("/admin-only", auth=SessionAuthSuperUser())
153+
def admin_view(request):
154+
return {"message": "Hello superuser!"}
155+
```
156+
157+
#### SessionAuthIsStaff
158+
159+
Authenticates users who are either superusers or staff members:
160+
161+
```python
162+
from ninja.security import SessionAuthIsStaff
163+
164+
@api.get("/staff-area", auth=SessionAuthIsStaff())
165+
def staff_view(request):
166+
return {"message": "Hello staff member!"}
167+
```
168+
169+
These authentication classes automatically use Django's `SESSION_COOKIE_NAME` setting and check the user's authentication status through the standard Django session framework.
170+
129171

130172

131173
### HTTP Bearer
@@ -166,6 +208,8 @@ or using router constructor
166208
router = Router(auth=BasicAuth())
167209
```
168210

211+
This overrides any API level authentication. To allow router operations to not use the API-level authentication by default, you can explicitly set the router's `auth=None`.
212+
169213

170214
## Custom exceptions
171215

docs/docs/guides/errors.md

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ Let's say you are making API that depends on some external service that is desig
88

99
To achieve that you need:
1010

11-
- 1) create some exception (or use existing one)
12-
- 2) use api.exception_handler decorator
11+
1. create some exception (or use existing one)
12+
2. use api.exception_handler decorator
1313

1414

1515
Example:
@@ -52,13 +52,17 @@ function must return http response
5252

5353
## Override the default exception handlers
5454

55-
By default, **Django Ninja** initialized the following exception handlers:
56-
55+
**Django Ninja** registers default exception handlers for the types shown below.
56+
You can register your own handlers with `@api.exception_handler` to override the default handlers.
5757

5858
#### `ninja.errors.AuthenticationError`
5959

6060
Raised when authentication data is not valid
6161

62+
#### `ninja.errors.AuthorizationError`
63+
64+
Raised when authentication data is valid, but doesn't allow you to access the resource
65+
6266
#### `ninja.errors.ValidationError`
6367

6468
Raised when request data does not validate
@@ -81,10 +85,17 @@ Default behavior
8185
- **else** - default django exception handler mechanism is used (error logging, email to ADMINS)
8286

8387

84-
### Override default handler
88+
## Customizing request validation errors
8589

86-
If you need to change default output for validation errors - override ValidationError exception handler:
90+
Requests that fail validation raise `ninja.errors.ValidationError` (not to be confused with `pydantic.ValidationError`).
91+
`ValidationError`s have a default exception handler that returns a 422 (Unprocessable Content) JSON response of the form:
92+
```json
93+
{
94+
"detail": [ ... ]
95+
}
96+
```
8797

98+
You can change this behavior by overriding the default handler for `ValidationError`s:
8899

89100
```python hl_lines="1 4"
90101
from ninja.errors import ValidationError
@@ -95,6 +106,36 @@ def validation_errors(request, exc):
95106
return HttpResponse("Invalid input", status=422)
96107
```
97108

109+
If you need even more control over validation errors (for example, if you need to reference the schema associated with
110+
the model that failed validation), you can supply your own `validation_error_from_error_contexts` in a `NinjaAPI` subclass:
111+
112+
```python hl_lines="4"
113+
from ninja.errors import ValidationError, ValidationErrorContext
114+
from typing import Any, Dict, List
115+
116+
class CustomNinjaAPI(NinjaAPI):
117+
def validation_error_from_error_contexts(
118+
self, error_contexts: List[ValidationErrorContext],
119+
) -> ValidationError:
120+
custom_error_infos: List[Dict[str, Any]] = []
121+
for context in error_contexts:
122+
model = context.model
123+
pydantic_schema = model.__pydantic_core_schema__
124+
param_source = model.__ninja_param_source__
125+
for e in context.pydantic_validation_error.errors(
126+
include_url=False, include_context=False, include_input=False
127+
):
128+
custom_error_info = {
129+
# TODO: use `e`, `param_source`, and `pydantic_schema` as desired
130+
}
131+
custom_error_infos.append(custom_error_info)
132+
return ValidationError(custom_error_infos)
133+
134+
api = CustomNinjaAPI()
135+
```
136+
137+
Now each `ValidationError` raised during request validation will contain data from your `validation_error_from_error_contexts`.
138+
98139

99140
## Throwing HTTP responses with exceptions
100141

0 commit comments

Comments
 (0)