Skip to content

Commit 3ee6b4d

Browse files
authored
Merge pull request #46 from eadwinCode/documentation_fix
Clear Written Documentation
2 parents 5cd509d + 6819332 commit 3ee6b4d

File tree

9 files changed

+466
-93
lines changed

9 files changed

+466
-93
lines changed

README.md

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

99
# Django Ninja Extra
1010

11-
**Django Ninja Extra** is a complete class-based fashion of building and setting up APIs at incredible speed with incredible performance.
12-
It utilizes [**Django Ninja**](https://django-ninja.rest-framework.com) core features without compromising speed.
11+
The **Django Ninja Extra** package offers a convenient, **class-based** approach to quickly building and configuring high-performance APIs.
12+
Utilizing the core features of [**Django Ninja**](https://django-ninja.rest-framework.com), it allows for speedy development without sacrificing performance."
1313

1414
**Key features:**
1515

docs/api_controller/index.md

Lines changed: 100 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,100 @@
11
# **Controller**
2+
The term 'APIController' is borrowed from the C# ASP.NET environment, which uses the MVC framework. Although Django is not an MVC framework, it is still possible to mimic the concept by using similar patterns and principles.
3+
Django-Ninja-Extra's APIController is modeled after the C# ASP.NET ApiController, providing an object-oriented approach to creating controller models and implementing modern software design patterns in your Django project. This allows you to use similar concepts and design patterns as in C# ASP.NET environment in your Django project.
24

3-
APIController is a borrowed term from the C# ASP.NET environment which is an MVC framework. Although Django is not an MVC framework, we can still mimic the concept generally just like any other programming concept.
5+
### Why APIController in Django.
46

5-
Django-Ninja-Extra APIController is modelled after C# ASP.NET ApiController, giving all OOP sense in creating your controller models and adapting recent software design patterns in your Django project.
7+
Coming from a background of modeling objects using class-based approaches, and having experience working with various API tools such as DRF, FastAPI, and Flask-Restful, you have likely noticed that these libraries primarily use function-based or class-tailored function-based approaches in writing route functions. This approach may not fully utilize the concepts of object-oriented programming when designing RESTful APIs. But despite this, these libraries are still great.
68

7-
### Why APIController in Django.
9+
I have designed the APIController in Django Ninja Extra to bring a more traditional controller-based approach to Django Ninja, providing more flexibility and adaptability to recent software design patterns.
10+
If you prefer using class-based controls for building APIs, Django Ninja Extra's APIController is a great option for you.
811

9-
I come from a background where we model anything in class based object, and I have worked with many API tools out there in python: DRF, FastAPI, Flask-Restful. It is either function based or class tailored to function based.
10-
Don't get me wrong, there are still great libraries. In fact, some features of Django-Ninja-Extra came from DRF. So I am a big fan. I needed more.
12+
## ControllerBase
1113

12-
I enjoyed Django ORM and I missed it while working with FastAPI but Django-Ninja became a saving grace. It brought FastAPI to Django in one piece. And it's super fast.
14+
The `ControllerBase` class is the base class for all controllers in Django Ninja Extra.
15+
It provides the core functionality for handling requests, validating input, and returning responses in a class-based approach.
1316

14-
So I designed APIController to extend Django-Ninja to class-based and have something more flexible to adapt to recent software design patterns out there.
17+
The class includes properties and methods that are common to all controllers, such as the `request` object, `permission_classes`, and `response` object which are part of the `RouteContext`.
18+
The request object contains information about the incoming request, such as headers, query parameters, and body data.
19+
The permission_classes property is used to define the permissions required to access the controller's routes,
20+
while the response object is used to construct the final response that is returned to the client.
1521

16-
So if you enjoy class-based controls for building API, welcome aboard.
22+
In addition to the core properties, the `ControllerBase` class also includes a number of utility methods that can be used to handle common tasks such as object permission checking (`check_object_permission`), creating quick responses (`create_response`), and fetching data from database (`get_object_or_exception`).
23+
These methods can be overridden in subclasses to provide custom behavior.
1724

18-
## ControllerBase
25+
The ControllerBase class also includes a **dependency injection** system that allows for easy access to other services and objects within the application, such as the repository services etc.
1926

2027
```python
21-
class ControllerBase(ABC):
28+
from ninja_extra import ControllerBase, api_controller
29+
30+
@api_controller('/users')
31+
class UserControllerBase(ControllerBase):
2232
...
2333
```
2434

25-
APIController decorates any class with `ControllerBase` if its not inheriting from it.
35+
36+
## APIController Decorator
37+
The `api_controller` decorator is used to define a class-based controller in Django Ninja Extra.
38+
It is applied to a ControllerBase class and takes several arguments to configure the routes and functionality of the controller.
39+
40+
The first argument, `prefix_or_class`, is either a prefix string for grouping all routes registered under the controller or the class object that the decorator is applied on.
41+
42+
The second argument, `auth`, is a list of all Django Ninja Auth classes that should be applied to the controller's routes.
43+
44+
The third argument, `tags`, is a list of strings for OPENAPI tags purposes.
45+
46+
The fourth argument, `permissions`, is a list of all permissions that should be applied to the controller's routes.
47+
48+
The fifth argument, `auto_import`, defaults to true, which automatically adds your controller to auto import list.
49+
50+
for example:
51+
52+
```python
53+
import typing
54+
from ninja_extra import api_controller, ControllerBase, permissions, route
55+
from django.contrib.auth.models import User
56+
from ninja.security import APIKeyQuery
57+
from ninja import ModelSchema
58+
59+
60+
class UserSchema(ModelSchema):
61+
class Config:
62+
model = User
63+
model_fields = ['username', 'email', 'first_name']
64+
65+
66+
@api_controller('users/', auth=[APIKeyQuery()], permissions=[permissions.IsAuthenticated])
67+
class UsersController(ControllerBase):
68+
@route.get('', response={200: typing.List[UserSchema]})
69+
def get_users(self):
70+
# Logic to handle GET request to the /users endpoint
71+
users = User.objects.all()
72+
return users
73+
74+
@route.post('create/', response={200: UserSchema})
75+
def create_user(self, payload: UserSchema):
76+
# Logic to handle POST request to the /users endpoint
77+
new_user = User.objects.create(
78+
username=payload.username,
79+
email=payload.email,
80+
first_name=payload.first_name,
81+
)
82+
new_user.set_password('password')
83+
return new_user
84+
85+
```
86+
87+
In the above code, we have defined a controller called `UsersController` using the `api_controller` decorator.
88+
The decorator is applied to the class and takes two arguments, the URL endpoint `/users` and `auth` and `permission` classes.
89+
And `get_users` and `create_user` are route function that handles GET `/users` and POST `/users/create` incoming request.
90+
2691

2792
!!!info
28-
Inheriting from ControllerBase class gives you more IDE intellisense support.
93+
Inheriting from ControllerBase class gives you more IDE intellisense support.
2994

3095
## Quick Example
3196

32-
Let's create an APIController to manage Django user model
97+
Let's create an APIController to properly manage Django user model
3398

3499
```python
35100
import uuid
@@ -79,3 +144,25 @@ class UsersController(ControllerBase):
79144
user = self.get_object_or_exception(self.user_model, id=user_id)
80145
return user
81146
```
147+
148+
In the example above, the `UsersController` class defines several methods that correspond to different HTTP methods,
149+
such as `create_user`, `update_user`, `delete_user`, `list_user` and `get_user_by_id`.
150+
These methods are decorated with `http_post`, `http_generic`, `http_delete`, `http_get` decorators respectively.
151+
152+
The `create_user` method uses `http_post` decorator and accepts a user argument of type `UserSchema`,
153+
which is a `ModelSchema` that is used to validate and serialize the input data.
154+
The method is used to create a new user in the system and return an `ID` of the user.
155+
156+
The `update_user` method uses `http_generic` decorator and accepts a `user_id` argument of type int.
157+
The decorator is configured to handle both `PUT` and `PATCH` methods and
158+
provides a response argument of type `UserSchema` which will be used to serialize the user object.
159+
160+
The `delete_user` method uses `http_delete` decorator and accepts a `user_id` argument of type int and a response argument of type
161+
Detail which will be used to return a 204 status code with an empty body on success.
162+
163+
The `list_user` method uses `http_get` decorator and decorated with `pagination.paginate` decorator that paginate the results of the method using `PageNumberPaginationExtra` class with page_size=50.
164+
It also provides a response argument of type `pagination.PaginatedResponseSchema[UserSchema]` which will be used to serialize and paginate the list of users returned by the method.
165+
166+
The `get_user_by_id` method uses `http_get` decorator and accepts a `user_id` argument of type int and a response argument of type UserSchema which will be used to serialize the user object.
167+
168+
The UsersController also use `self.get_object_or_exception(self.user_model, id=user_id)` which is a helper method that will raise an exception if the user object is not found.

docs/index.md

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

99
# Django Ninja Extra
1010

11-
**Django Ninja Extra** is a complete class-based fashion of building and setting up APIs at incredible speed with incredible performance.
12-
It utilizes [**Django Ninja**](https://django-ninja.rest-framework.com) core features without compromising speed.
11+
The **Django Ninja Extra** package offers a convenient, **class-based** approach to quickly building and configuring high-performance APIs.
12+
Utilizing the core features of [**Django Ninja**](https://django-ninja.rest-framework.com), it allows for speedy development without sacrificing performance."
1313

1414
**Key features:**
1515

@@ -35,9 +35,6 @@ Plus **Extra**:
3535
- pydantic >= 1.6
3636
- Django-Ninja >= 0.16.1
3737

38-
## Django-Ninja Benchmark
39-
**Django-Ninja-Extra** uses **Django-Ninja** under the hood, it can be assumed that Django-Ninja-Extra has the same benchmark with Django-Ninja
40-
![Django Ninja REST Framework](images/benchmark.png)
4138

4239
Full documentation, [visit](https://eadwincode.github.io/django-ninja-extra/).
4340

docs/route_context.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
**Django Ninja Extra** provides the RouteContext object, which is available throughout the request lifecycle.
2+
This object holds essential properties for the route handler that will handle the request.
3+
These properties include the Django `HttpRequest` object, a list of permission classes for the route handler,
4+
a temporary response object used by Django-Ninja to construct the final response,
5+
and kwargs and args required for calling the route function.
6+
7+
It's important to note that these properties are not set at the beginning of the request,
8+
but rather become available as the request progresses through different stages,
9+
and before it reaches the route function execution.
10+
11+
12+
```python
13+
from pydantic import BaseModel as PydanticModel, Field
14+
15+
class RouteContext(PydanticModel):
16+
"""
17+
APIController Context which will be available to the class instance when handling request
18+
"""
19+
20+
class Config:
21+
arbitrary_types_allowed = True
22+
23+
permission_classes: PermissionType = Field([])
24+
request: Union[Any, HttpRequest, None] = None
25+
response: Union[Any, HttpResponse, None] = None
26+
args: List[Any] = Field([])
27+
kwargs: DictStrAny = Field({})
28+
```
29+
30+
## How to Access `RouteContext`
31+
32+
In Django Ninja Extra, the `RouteContext` object can be accessed within the **controller class** by using the `self.context` property.
33+
This property is available at the instance level of the controller class, making it easy to access the properties and methods of the `RouteContext` object.
34+
35+
For example.
36+
```python
37+
from ninja_extra import ControllerBase, api_controller, route
38+
from django.db import transaction
39+
from ninja_extra.permissions import IsAuthenticated
40+
from ninja_jwt.authentication import JWTAuth
41+
from django.contrib.auth import get_user_model
42+
43+
User = get_user_model()
44+
45+
46+
@api_controller("/books", auth=JWTAuth(), permissions=[IsAuthenticated])
47+
class StoryBookSubscribeController(ControllerBase):
48+
@route.get(
49+
"/context",
50+
url_name="subscribe",
51+
)
52+
@transaction.atomic
53+
def subscribe(self):
54+
user = self.context.request.user
55+
return {'message': 'Authenticated User From context', 'email': user.email}
56+
57+
@route.post(
58+
"/context",
59+
url_name="subscribe",
60+
)
61+
@transaction.atomic
62+
def subscribe_with_response_change(self):
63+
res = self.context.response
64+
res.headers.setdefault('x-custom-header', 'welcome to custom header in response')
65+
return {'message': 'Authenticated User From context and Response header modified', 'email': self.context.request.user.email}
66+
67+
```
68+
69+
In the example, we can access the authenticated `user` object from the request object in the `self.context` property, which is available in the controller class.
70+
This allows us to easily access the authenticated user's information
71+
72+
### Modifying Response Header with RouteContext
73+
74+
The `RouteContext` object provides you with the necessary properties and methods to manipulate the response data before it is returned to the client.
75+
With the RouteContext object, you can easily modify header, status, or cookie data for the response returned for a specific request
76+
77+
For example, lets add extra `header` info our new endpoint, `subscribe_with_response_change` as shown below.
78+
```python
79+
from ninja_extra import ControllerBase, api_controller, route
80+
from django.db import transaction
81+
from ninja_extra.permissions import IsAuthenticated
82+
from ninja_jwt.authentication import JWTAuth
83+
from django.contrib.auth import get_user_model
84+
85+
User = get_user_model()
86+
87+
88+
@api_controller("/books", auth=JWTAuth(), permissions=[IsAuthenticated])
89+
class StoryBookSubscribeController(ControllerBase):
90+
@route.post(
91+
"/context-response",
92+
url_name="response",
93+
)
94+
@transaction.atomic
95+
def subscribe_with_response_change(self):
96+
res = self.context.response
97+
res.headers['x-custom-header'] = 'welcome to custom header in response'
98+
return {'message': 'Authenticated User From context and Response header modified', 'email': self.context.request.user.email}
99+
100+
```
101+
102+
## Using `RouteContext` in Schema
103+
104+
There may be situations where you need to access the request object during schema validation.
105+
Django Ninja Extra makes this easy by providing a way to resolve the `RouteContext` object during the request,
106+
which can then be used to access the request object and any other necessary properties.
107+
This allows you to use the context of the request within the validation process, making it more flexible and powerful.
108+
109+
For example:
110+
111+
```python
112+
from typing import Optional
113+
from django.urls import reverse
114+
from ninja_extra import service_resolver
115+
from ninja_extra.controllers import RouteContext
116+
from ninja import ModelSchema
117+
from pydantic import AnyHttpUrl, validator
118+
119+
120+
class StoreBookSchema(ModelSchema):
121+
borrowed_by: Optional[UserRetrieveSchema]
122+
store: AnyHttpUrl
123+
book: BookSchema
124+
125+
class Config:
126+
model = StoreBook
127+
model_fields = ['borrowed_by', 'store', 'book']
128+
129+
@validator("store", pre=True, check_fields=False)
130+
def store_validate(cls, value_data):
131+
context: RouteContext = service_resolver(RouteContext)
132+
value = reverse("store:detail", kwargs=dict(store_id=value_data.id))
133+
return context.request.build_absolute_uri(value)
134+
```
135+
136+
In the example above, we used the `service_resolver`, a dependency injection utility function, to resolve the `RouteContext` object.
137+
This gave us access to the request object, which we used to construct a full URL for our store details.
138+
By using the `service_resolver` to access the RouteContext, we can easily access the request object,
139+
and use it to gather any necessary information during the validation process.

0 commit comments

Comments
 (0)